From 110b603b9f28037daa8dc245d690cbff2813c1d5 Mon Sep 17 00:00:00 2001 From: Shai Szulanski Date: Mon, 27 Nov 2023 16:53:11 -0800 Subject: [PATCH] Consolidate annotations documentation Summary: Move documentation for structured annotations to their docblocks, transcluded into one page in static docs that also holds legacy content. Merge other two pages into this one. Reviewed By: vitaut Differential Revision: D51532913 fbshipit-source-id: df5c57c7dc772f7b75cafb5c8565814c2eba9391 --- thrift/annotation/cpp.thrift | 97 +++++++++++++++++++-- thrift/annotation/hack.thrift | 145 +++++++++++++++++++++++--------- thrift/annotation/python.thrift | 81 +++++++++--------- thrift/annotation/scope.thrift | 4 +- thrift/annotation/thrift.thrift | 39 ++++++++- 5 files changed, 272 insertions(+), 94 deletions(-) diff --git a/thrift/annotation/cpp.thrift b/thrift/annotation/cpp.thrift index bd0b2bc8d..d55bdf522 100644 --- a/thrift/annotation/cpp.thrift +++ b/thrift/annotation/cpp.thrift @@ -25,9 +25,23 @@ namespace py.asyncio facebook_thrift_asyncio.annotation.cpp namespace go thrift.annotation.cpp namespace py thrift.annotation.cpp +// start + /** - * Changes the native type of a Thrift object. - * `name` and `template` correspond to `cpp.type` and `cpp.template` respecively. + * Changes the native type of a Thrift object (the C++ type used in codegen) to the value of the `name` field. + * Container types may instead provide the `template` field, in which case template parameters will be filled in by thrift. + * (e.g. `template = "folly::sorted_vector_set"` is equivalent to `type = "folly::sorted_vector_set"` on `set`) + * + * It is also possible to add `cpp_include` to bring in additional data structures and use them here. + * It is required that the custom type matches the specified Thrift type even for internal container types. + * Prefer types that can leverage `reserve(size_t)` as Thrift makes uses these optimizations. + * *Special Case*: This annotation can be used to define a string/binary type as `IOBuf` or `unique_ptr` so that you can leverage Thrift's support for zero-copy buffer manipulation through `IOBuf`. + * During deserialization, thrift receives a buffer that is used to allocate the appropriate fields in the struct. When using smart pointers, instead of making a copy of the data, it only modifies the pointer to point to the address that is used by the buffer. + * + * The custom type must provide the following methods + * * `list`: `push_back(T)` + * * `map`: `insert(std::pair)` + * * `set`: `insert(T)` */ @scope.Typedef @scope.Field @@ -36,25 +50,51 @@ struct Type { 2: string template (cpp.name = "template_"); } -enum RefType { - Unique = 0, - Shared = 1, - SharedMutable = 2, -} - +/** + * Allocates a field on the heap instead of inline. + * This annotation is added to support recursive types. However, you can also use it to turn a field from a value to a smart pointer. + * `@cpp.Ref` is equivalent having type`@cpp.RefType.Unique`. + * + * NOTE: A struct may transitively contain itself as a field only if at least one of the fields in the inclusion chain is either an optional Ref field or a container. Otherwise the struct would have infinite size. + */ @scope.Field struct Ref { - 1: RefType type; + 1: RefType type; /// Optional, defaults to Unique +} +enum RefType { + Unique = 0, /// `std::unique_ptr` + Shared = 1, /// `std::shared_ptr ` + SharedMutable = 2, /// `std::shared_ptr` } /** * Changes the name of the definition in generated C++ code. + * In most cases a much better solution is to rename the problematic Thrift field itself. Only use the `cpp.name` annotation if such renaming is problematic, + * e.g. when the field name appears in code as a string, particularly when using JSON serialization, and it is hard to change all usage sites. */ @scope.Definition struct Name { 1: string value; } +/** + Lazily deserialize large field on first access. + + ``` + FooWithLazyField foo; + apache::thrift::CompactSerializer::deserialize(serializedData, foo); + + // large_field is lazy field, it will be deserialized on first access + // The data will be deserialized in method call large_field_ref() + LOG(INFO) << foo.large_field_ref()->size(); + + // Result will be cached, we won't deserialize again + LOG(INFO) << foo.large_field_ref()->size(); + ``` + + Read more: /doc/fb/languages/cpp/lazy.md +*/ + @scope.Field struct Lazy { // Use std::unique_ptr instead of folly::IOBuf to store serialized data. @@ -136,11 +176,43 @@ struct Adapter { 5: bool moveOnly; } +/** +* Packs isset bits into fewer bytes to save space at the cost of making access more expensive. +* Passing `atomic = false` reduces the access cost while making concurrent writes UB. +* Read more: /doc/fb/languages/cpp/isset-bitpacking.md +*/ @scope.Struct struct PackIsset { 1: bool atomic = true; } +/** + This annotation enables reordering of fields in the generated C++ struct to minimize padding. + This is achieved by placing the fields in the order of decreasing alignments. The order of fields with the same alignment is preserved. + + ``` + @cpp.MinimizePadding + struct Padded { + 1: byte small + 2: i64 big + 3: i16 medium + 4: i32 biggish + 5: byte tiny + } + ``` + + For example, the C++ fields for the `Padded` Thrift struct above will be generated in the following order: + + ``` + int64_t big; + int32_t biggish; + int16_t medium; + int8_t small; + int8_t tiny; + ``` + + which gives the size of 16 bytes compared to 32 bytes if `cpp.MinimizePadding` was not specified. +*/ @scope.Struct struct MinimizePadding {} @@ -215,6 +287,7 @@ struct UseOpEncode {} /** * Enum in C++ by default uses signed 32 bit integer. There is no need to specify * underlying type for signed 32 bit integer. + * 64-bit is not supported to avoid truncation since enums are sent as 32-bit integers over the wire. */ enum EnumUnderlyingType { /** ::std::int8_t */ @@ -291,6 +364,12 @@ struct GenerateTypedInterceptor {} * Causes C++ handler code to run inline on the EventBase thread. * Disables overload protection, use with caution. * Cannot be applied to individual functions in interactions. + * + * Causes the request to be executed on the event base thread directly instead of rescheduling onto a thread manager thread, provided the async_eb_ handler method is implemented. + * You should only execute the request on the event base thread if it is very fast and you have measured that rescheduling is a substantial chunk of your service's CPU usage. + * If a request executing on the event base thread blocks or takes a long time, all other requests sharing the same event base are affected and latency will increase significantly. + * We strongly discourage the use of this annotation unless strictly necessary. You will have to implement the harder-to-use async_eb_ handler method. + * This also disables queue timeouts, an important form of overload protection. */ @scope.Function @scope.Interaction diff --git a/thrift/annotation/hack.thrift b/thrift/annotation/hack.thrift index e6a587da5..9ae960ba0 100644 --- a/thrift/annotation/hack.thrift +++ b/thrift/annotation/hack.thrift @@ -24,57 +24,80 @@ namespace py.asyncio facebook_thrift_asyncio.annotation.hack namespace go thrift.annotation.hack namespace hs2 facebook.thrift.annotation.hack -// An experimental annotation that applies a Hack wrapper to fields. -// For example: -// -// struct User { -// @hack.FieldWrapper{name="MyWrapper"} -// 1: i64 id; -// } -// +// start + +/// An experimental annotation that applies a Hack wrapper to fields. +/// For example: +/// +/// struct User { +/// @hack.FieldWrapper{name="MyWrapper"} +/// 1: i64 id; +/// } @scope.Field struct FieldWrapper { - // The name of a Hack wrapper class used to wrap the field + /// The name of a Hack wrapper class used to wrap the field 1: string name; } -// An annotation that applies a Hack wrapper to fields, typedef or structs. -// For example: -// -// struct User { -// @hack.FieldWrapper{name="MyWrapper"} -// 1: i64 id; -// } -// +/// An annotation that applies a Hack wrapper to fields, typedef or structs. +/// For example: +/// +/// struct User { +/// @hack.FieldWrapper{name="MyWrapper"} +/// 1: i64 id; +/// } @scope.Typedef @scope.Struct @scope.Field struct Wrapper { - // The name of a Hack wrapper class used to wrap the field + /// The name of a Hack wrapper class used to wrap the field 1: string name; - // When applied directly to a typedef or struct, the IDL name of the - // type will refer to the adapted type in Hack and the underlying thrift struct will be - // generated in a nested namespace and/or with a different name. By default the type/struct - // will be generated in a nested 'thrift_adapted_types' namespace with the same name, - // but both of these can be changed by setting these fields. - // Empty string enables the nested namespace and uses the IDL name for the struct. + /// When applied directly to a typedef or struct, the IDL name of the + /// type will refer to the adapted type in Hack and the underlying thrift struct will be + /// generated in a nested namespace and/or with a different name. By default the type/struct + /// will be generated in a nested 'thrift_adapted_types' namespace with the same name, + /// but both of these can be changed by setting these fields. + /// Empty string enables the nested namespace and uses the IDL name for the struct. 2: string underlyingName; 3: string extraNamespace = "thrift_adapted_types"; } (thrift.uri = "facebook.com/thrift/annotation/hack/Wrapper") -// An annotation that applies a Hack adapter to types. For example: -// @hack.Adapter{name="\\TimestampAdapter"} -// typedef i64 Timestamp; -// -// struct User { -// 1: Timestamp account_creation_time; -// } -// -// Here the field `account_creation_time` will have type TimestampAdapter::THackType instead of i64. +/// An annotation that applies a Hack adapter to types. For example: +/// @hack.Adapter{name="\\TimestampAdapter"} +/// typedef i64 Timestamp; +/// +/// struct User { +/// 1: Timestamp account_creation_time; +/// } +/// +/// Here the field `account_creation_time` will have type TimestampAdapter::THackType instead of i64. +/// +/// in hack: +/// ``` +/// final class TimestampAdapter implements IThriftAdapter { +/// const type TThriftType = int; +/// const type THackType = Time; +/// public static function fromThrift(int $seconds)[]: Time { +/// return Time::fromEpochSeconds($seconds); +/// } +/// public static function toThrift(Time $time): int { +/// return $hack_value->asFullSecondsSinceEpoch(); +/// } +/// } +/// ``` +/// elsewhere in hack: +/// ``` +/// function timeSinceCreated(Document $doc): Duration { +/// // $doc->created_time is of type Time +/// return Duration::between(Time::now(), $doc->created_time); +/// } +/// ``` +/// This completely replaces the underlying type of a thrift for a custom implementation and uses +/// the specified adapter to convert to and from the underlying Thrift type during (de)serialization. @scope.Typedef @scope.Field struct Adapter { - // The name of a Hack adapter class that implements IThriftAdapter + /// The name of a Hack adapter class that implements IThriftAdapter 1: string name; } @@ -85,23 +108,23 @@ struct SkipCodegen { 1: string reason; } -// This annotation is mainly used to rename symbols which can result in symbol -// conflict errors in Hack codegen. -// For ex: reserved keywords in Hack language, symbols with similar names from -// other files in Hack +/// This annotation is mainly used to rename symbols which can result in symbol +/// conflict errors in Hack codegen. +/// For ex: reserved keywords in Hack language, symbols with similar names from +/// other files in Hack @scope.Definition struct Name { 1: string name; 2: string reason; } -// This annotation is for adding Hack attributes to union enums. +/// This annotation is for adding Hack attributes to union enums. @scope.Union struct UnionEnumAttributes { 1: list attributes; } -// This annotation is for using a custom trait for structs. +/// This annotation is for using a custom trait for structs. @scope.Struct @scope.Union @scope.Exception @@ -109,7 +132,45 @@ struct StructTrait { 1: string name; } -// This annotation is for adding Hack attributes. +/// This annotation is for adding Hack attributes. +/// * Where to use: field or struct type +/// * Value: add attributes like `JSEnum` to structs or fields +/// * Example: + +/// ``` +/// // In thrift +/// enum MyEnum { +/// ALLOWED = 1, +/// THIS_IS_ALLOWED = 2, +/// THIS_IS_ALLOWED_2 = 3, +/// }( +/// hack.attributes= +/// "\JSEnum(shape('name' => 'MyEnum')), +/// \GraphQLEnum('MyEnum', 'Description for my enum',)" +/// ) +/// struct MyThriftStruct { +/// 1: string foo (hack.attributes = "FieldAttribute"); +/// 2: string bar; +/// 3: string baz; +/// } (hack.attributes = "ClassAttribute") +/// ``` +/// ``` +/// //thrift compiler will generate this for you +/// <<\JSEnum(shape('name' => 'MyEnum')), +/// \GraphQLEnum('MyEnum', 'Description for my enum',)>> +/// enum MyEnum: int { +/// ALLOWED = 1; +/// THIS_IS_ALLOWED = 2; +/// THIS_IS_ALLOWED_2 = 3; +/// } +/// <> +/// class MyThriftStruct implements \IThriftStruct { +/// .... +/// <> +/// public string $foo; +/// .... +/// } +/// ``` struct Attributes { 1: list attributes; } @@ -119,7 +180,7 @@ struct Attributes { @scope.Exception struct StructAsTrait {} -// This annotation is to generate an entity as internal +/// This annotation is to generate an entity as internal @scope.Struct @scope.Union @scope.Enum diff --git a/thrift/annotation/python.thrift b/thrift/annotation/python.thrift index 2a1d88104..d7bf9ab2c 100644 --- a/thrift/annotation/python.thrift +++ b/thrift/annotation/python.thrift @@ -23,7 +23,9 @@ namespace py.asyncio facebook_thrift_asyncio.annotation.python namespace go thrift.annotation.python namespace py thrift.annotation.python -// Hides in thrift-py3 only, not in thrift-python +// start + +/// Hides in thrift-py3 only, not in thrift-python @scope.Definition struct Py3Hidden {} @@ -35,52 +37,53 @@ struct Name { 1: string name; } -// An annotation that applies a Python adapter to typedef or field, or directly on struct. -// -// -// Example 1: -// -// @python.Adapter{name = "my.module.DatetimeAdapter", typeHint = "datetime.datetime"} -// typedef i64 Datetime -// -// Here the type 'Datetime' has the Python adapter `DatetimeAdapter`. -// -// -// Example 2: -// -// struct User { -// @python.Adapter{name = "my.module.DatetimeAdapter", typeHint = "datetime.datetime"} -// 1: i64 created_at; -// } -// Here the field `created_at` has the Python adapter `DatetimeAdapter`. -// -// -// Example 3: -// -// -// @python.Adapter{name = "my.module.AnotherAdapter", typeHint = "my.module.AdaptedFoo"} -// struct Foo { -// 1: string bar; -// } -// -// Here the struct `Foo` has the Python adapter `AnotherAdapter`. -// +/// An annotation that applies a Python adapter to typedef or field, or directly on struct. +/// This completely replaces the underlying type of a thrift for a custom implementation and +/// uses the specified adapter to convert to and from the underlying Thrift type during (de)serialization. +/// +/// Example 1: +/// +/// @python.Adapter{name = "my.module.DatetimeAdapter", typeHint = "datetime.datetime"} +/// typedef i64 Datetime +/// +/// Here the type 'Datetime' has the Python adapter `DatetimeAdapter`. +/// +/// +/// Example 2: +/// +/// struct User { +/// @python.Adapter{name = "my.module.DatetimeAdapter", typeHint = "datetime.datetime"} +/// 1: i64 created_at; +/// } +/// Here the field `created_at` has the Python adapter `DatetimeAdapter`. +/// +/// +/// Example 3: +/// +/// +/// @python.Adapter{name = "my.module.AnotherAdapter", typeHint = "my.module.AdaptedFoo"} +/// struct Foo { +/// 1: string bar; +/// } +/// +/// Here the struct `Foo` has the Python adapter `AnotherAdapter`. +/// @scope.Field @scope.Typedef @scope.Structured struct Adapter { - // Fully qualified name of a Python adapter class, which should inherit from thrift.python.adapter.Adapter + /// Fully qualified name of a Python adapter class, which should inherit from thrift.python.adapter.Adapter 1: string name; - // Fully qualified type hint the above implementation adapts to. - // If ending with "[]", it becomes a generic, and the unadapted type will be filled between the brackets. + /// Fully qualified type hint the above implementation adapts to. + /// If ending with "[]", it becomes a generic, and the unadapted type will be filled between the brackets. 2: string typeHint; } (thrift.uri = "facebook.com/thrift/annotation/python/Adapter") -// Controls cpp <-> python FFI for a struct or union -// By default, struct uses marshal C API unless cpp.Type or cpp.Adapter is present -// on a field or a type -// Use this annotation to opt-in struct to marshal in spite of cpp.Type or cpp.Adapter -// Alternatively, use this struct with serialize = false to use serialization for FFI. +/// Controls cpp <-> python FFI for a struct or union +/// By default, struct uses marshal C API unless cpp.Type or cpp.Adapter is present +/// on a field or a type +/// Use this annotation to opt-in struct to marshal in spite of cpp.Type or cpp.Adapter +/// Alternatively, use this struct with serialize = false to use serialization for FFI. @scope.Structured struct UseCAPI { 1: bool serialize = false; diff --git a/thrift/annotation/scope.thrift b/thrift/annotation/scope.thrift index 9c07cb93e..a5489885b 100644 --- a/thrift/annotation/scope.thrift +++ b/thrift/annotation/scope.thrift @@ -14,9 +14,11 @@ * limitations under the License. */ +// start + /** * Annotations that indicate which IDL definition a structured annotation can - * be place on. + * be placed on. * * For example: * include "thrift/annotation/scope.thrift" diff --git a/thrift/annotation/thrift.thrift b/thrift/annotation/thrift.thrift index 5f917f580..9af0c466a 100644 --- a/thrift/annotation/thrift.thrift +++ b/thrift/annotation/thrift.thrift @@ -24,6 +24,8 @@ namespace py.asyncio facebook_thrift_asyncio.annotation.thrift namespace go thrift.annotation.thrift namespace py thrift.annotation.thrift +// start + /** * Indicates a definition/feature should only be used with permission, may * only work in specific contexts, and may change in incompatible ways without @@ -102,7 +104,36 @@ struct RequiresBackwardCompatibility { @Experimental struct TerseWrite {} -/** Indicates that a field's value should never be stored on the stack. */ +/** Indicates that an optional field's value should never be stored on the stack, +i.e. the subobject should be allocated separately (e.g. because it is large and infrequently set). + +NOTE: The APIs and initialization behavior are same as normal field, but different from `@cpp.Ref`. e.g. + +``` +struct Foo { + 1: optional i32 normal; + @thrift.Box + 2: optional i32 boxed; + @cpp.Ref + 3: optional i32 referred; +} +``` +in C++ + +``` +Foo foo; +EXPECT_FALSE(foo.normal().has_value()); // okay +EXPECT_FALSE(foo.boxed().has_value()); // okay +EXPECT_FALSE(foo.referred().has_value()); // build failure: std::unique_ptr doesn't have has_value method + +EXPECT_EQ(*foo.normal(), 0); // throw bad_field_access exception +EXPECT_EQ(*foo.boxed(), 0); // throw bad_field_access exception +EXPECT_EQ(*foo.referred(), 0); // okay, field has value by default +``` + +Affects C++ and Rust. +TODO: replace with @cpp.Box + @rust.Box +*/ @scope.Field struct Box {} @@ -111,10 +142,12 @@ struct Box {} struct Mixin {} /** - * Option to serialize thrift struct in ascending field id order. + * Option to serialize thrift struct in ascending field id order instead of field declaration order. * * This can potentially make serialized data size smaller in compact protocol, - * since compact protocol can write deltas between subsequent field ids. + * since compact protocol can write deltas between subsequent field ids instead of full ids. + * + * NOTE: This annotation won't reduce payload size for other protocols. */ @scope.Struct @Experimental // TODO(ytj): Release to Beta.