Following the official Go release policy, we support the latest two Go releases at the time of the Thrift release.
For example, at the time of Thrift v0.14.0 release, the latest two Go releases are go1.15 and go1.14, and those are the two Go releases supported by Thrift v0.14.* (including v0.14.1 and v0.14.2 patch releases).
Because of Go’s backward compatibility guarantee, older Thrift libraries usually works with newer Go releases (e.g. Thrift v0.14.* works with go1.16, although it’s not officially supported), but newer Thrift releases might use new APIs introduced in Go releases and no longer work with older Go releases. For example, Thrift v0.14.0 used APIs introduced in go1.13, and as a result no longer works on go1.12.
Thrift supports the currently officially supported Go releases (the latest 2).
After initializing the go modules file in your project, use the following command to add the most recent version of the package:
$ go get github.com/apache/thrift
The thrift-to-Go compiler tries to represent thrift IDL structs as Go structs. We must be able to distinguish between optional fields that are set to their default value and optional values which are actually unset, so the generated code represents optional fields via pointers.
This is generally intuitive and works well much of the time, but Go does not have a syntax for creating a pointer to a constant in a single expression. That is, given a struct like
struct SomeIDLType {
OptionalField *int32
}
, the following will not compile:
x := &SomeIDLType{
OptionalField: &(3),
}
(Nor is there any other syntax that’s built in to the language)
As such, we provide some helpers that do just this under lib/go/thrift/. E.g.,
x := &SomeIDLType{
OptionalField: thrift.Int32Ptr(3),
}
And so on. The code generator also creates analogous helpers for user-defined typedefs and enums.
You can add tags to the auto-generated thrift structs using the following format:
struct foo {
1: required string Bar (go.tag = "some_tag:\"some_tag_value\"")
}
which will generate:
type Foo struct {
Bar string `thrift:"bar,1,required" some_tag:"some_tag_value"`
}
The context object passed into the server handler function will be canceled when
the client closes the connection (this is a best effort check, not a guarantee
– there’s no guarantee that the context object is always canceled when client
closes the connection, but when it’s canceled you can always assume the client
closed the connection). The cause of the cancellation (via context.Cause(ctx))
would also be set to thrift.ErrAbandonRequest.
When implementing Go Thrift server, you can take advantage of that to abandon
requests that’s no longer needed by returning thrift.ErrAbandonRequest:
func MyEndpoint(ctx context.Context, req *thriftRequestType) (*thriftResponseType, error) {
...
if ctx.Err() == context.Canceled {
return nil, thrift.ErrAbandonRequest
// Or just return ctx.Err(), compiler generated processor code will
// handle it for you automatically:
// return nil, ctx.Err()
}
...
}
This feature would add roughly 1 millisecond of latency overhead to the server handlers (along with roughly 2 goroutines per request). If that is unacceptable, it can be disabled by having this line early in your main function:
thrift.ServerConnectivityCheckInterval = 0
Please be advised that due to a Go runtime bug, currently if this interval is set to a value too low (for example, 1ms), it might cause excessive cpu overhead.
This feature is also only enabled on non-oneway endpoints.
Go maps cannot be keyed by a slice or a map, so a thrift map whose key type
is a list, set, map, or a typedef of one of those is generated as a
slice of key/value pairs instead:
struct S { 1: map<list<string>, i32> m }
type S struct {
M []thrift.MapEntry[[]string, int32]
}
Entries are written to the wire in slice order. As with set fields, writing
a slice that contains two equal keys fails with an INVALID_DATA protocol
exception. A map is unordered, so Equals compares the entries as a
collection: two values holding the same entries in a different order are
equal. Maps keyed by binary remain Go maps keyed by string.
Maps keyed by a struct, an exception or a union are generated as map[*K]V by
default. Because the key is a pointer, two decoded keys with equal contents are
distinct map keys, and Equals compares them by identity. Pass the
struct_key_entries option to generate those fields as
[]thrift.MapEntry[*K, V] as well:
thrift --gen go:struct_key_entries file.thrift
The option changes more than the Go type. Equals starts comparing key
contents, which is the point. Writing a slice that holds two keys with equal
contents fails with INVALID_DATA, where the map form silently wrote both.
How that uniqueness check is done depends on the key. When every field of the
key struct is a non-pointer scalar, the struct value is itself a valid Go map
key and == on it agrees with Equals, so the check collects the keys in a
set and runs in linear time. In a local benchmark, writing a field of 5000 such
keys came within about 20 percent of the same data in map[*K]V, at the cost
of one temporary set sized to the entry count.
Any other key falls back to comparing every pair, which is quadratic: 5000
entries cost about 12.5 million calls to Equals on every write. A key struct
takes that path if it has an optional field, a field holding another struct, or
a binary, list, set or map field, because none of those compare by
content under ==. Unions always take it, since all of their fields are
optional. Container keys take it too. Keep those maps small, or keep the key
struct to plain scalars.
A typedef of a struct used as a key is the struct itself, since the generated
type KeyAlias = Key is an alias, so the entry type is []thrift.MapEntry[*Key, V]
and the key compares like Key does.
A typedef of a struct, union or exception is generated as a Go type alias:
typedef Inner Alias
type Alias = Inner
Alias is the struct itself, with its Read, Write and Equals methods,
and a field or argument declared with it is a *Inner like any other struct
field. Earlier releases generated type Alias *Inner, a defined type over a
pointer with no methods, so a package that used such a typedef in a field or a
service signature did not compile. A typedef of a base type is unchanged and
stays a defined type, type TimestampMilliseconds int64, with its <Name>Ptr
helper.
What changes for hand-written code that used an alias the IDL declared but never used, the only shape that compiled before:
*Inner; it now holds an Inner, and
the pointer moves outside the alias (*Alias).<Name>Ptr helper is no longer generated for such a typedef, because
the struct has none either.%T prints the struct’s name rather than the alias.An enum holding an integer the IDL does not define used to print <UNSET>
from String(), which reads as “no value was sent” even though the integer
arrived intact and is written back to the wire unchanged. String() now
formats such a value as Color(999), the shape stringer produces.
MarshalText, and with it encoding/json, follow String(), so the text and
JSON encoding of an undefined value changes the same way. Neither form
round-trips through UnmarshalText, which still rejects any string that is not
a defined name.
Every enum also gains an IsDefined() bool method that reports whether the
value is one of the IDL-defined constants. Code that compared String()
against the literal "<UNSET>" should call IsDefined() instead.
TSimpleServer.Stop will wait for all client connections to be closed after the last received request to be handled, as the time spent by Stop may sometimes be too long:
To prevent Stop from hanging for too long, you can set thrift.ServerStopTimeout in your main or init function:
thrift.ServerStopTimeout = <max_duration_to_stop>
If it’s set to <=0, the feature will be disabled (by default), and server will wait for all the client connections to be closed gracefully with zero err time. Otherwise, the stop will wait for all the client connections to be closed gracefully util thrift.ServerStopTimeout is reached, and client connections that are not closed after thrift.ServerStopTimeout will be closed abruptly which may cause some client errors.
The TLS socket constructors no longer pin a minimum protocol version of their
own when the caller leaves tls.Config.MinVersion unset. Such a config now
follows the crypto/tls default minimum – TLS 1.2 in current Go releases –
where the constructors previously set TLS 1.0.
A MinVersion the caller sets is left untouched, so a caller that needs an
older floor can ask for it explicitly:
conf := &thrift.TConfiguration{
TLSConfig: &tls.Config{MinVersion: tls.VersionTLS10},
}
THeaderTransport.Flush now holds the frames it writes to the transport’s
configured MaxFrameSize, the limit ReadFrame already applies to the frames
it reads. A larger frame is not written; Flush returns a
TProtocolException of type SIZE_LIMIT instead. The same applies to a
header block larger than the frame format can describe (65535 words of four
bytes).
A client that sends frames larger than the default of 16384000 bytes needs a
larger MaxFrameSize in its own configuration, not only in the server’s:
conf := &thrift.TConfiguration{
MaxFrameSize: 64 * 1024 * 1024,
}
TFramedTransport.Flush holds the frames it writes to the same limit, through
the same check. A larger frame is not written: Flush returns a
TProtocolException of type SIZE_LIMIT and drops the frame, where it
previously returned no error and wrote a frame readFrame refuses. The note
above on raising MaxFrameSize applies to it as well.