Skip to content

Commit b101f74

Browse files
echistyakovmeta-codesync[bot]
authored andcommitted
Add Sink documentation
Summary: TSIA Reviewed By: podtserkovskiy Differential Revision: D94939603 fbshipit-source-id: a6cc8753f7094ceaca26c4b1ae05d390151c587f
1 parent bf0b034 commit b101f74

1 file changed

Lines changed: 392 additions & 0 deletions

File tree

  • third-party/thrift/src/thrift/lib/go/thrift/docs
Lines changed: 392 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,392 @@
1+
# Thrift Sink in Go
2+
3+
This document explains how to use Thrift Sink in Go, both from the client side
4+
and server side.
5+
6+
For general information about Thrift Sink concepts and how to define sink
7+
services in Thrift IDL, please refer to the
8+
[Thrift Streaming Wiki](https://www.internalfb.com/intern/staticdocs/thrift/docs/fb/features/streaming/).
9+
10+
## Table of Contents
11+
12+
- [Overview](#overview)
13+
- [Defining Sink Services](#defining-sink-services)
14+
- [Client-Side Usage](#client-side-usage)
15+
- [Server-Side Implementation](#server-side-implementation)
16+
17+
## Overview
18+
19+
Thrift Sink allows the client to stream data to the server (client-to-server
20+
streaming), enabling efficient handling of large uploads or continuous data
21+
feeds. The client produces a stream of elements that the server consumes, and
22+
the server returns a final response after processing all elements. Go's
23+
implementation uses `iter.Seq2` iterators to provide an idiomatic API for sink
24+
functionality.
25+
26+
**Key differences from Streaming:**
27+
28+
| Feature | Streaming | Sink |
29+
| ----------------- | --------------------- | --------------------- |
30+
| Data direction | Server → Client | Client → Server |
31+
| Producer | Server | Client |
32+
| Consumer | Client | Server |
33+
| Final response | None | Server sends after consuming all elements |
34+
35+
## Defining Sink Services
36+
37+
Define sink methods in your Thrift IDL file using the `sink<ElemType, FinalResponseType>`
38+
return type:
39+
40+
```thrift
41+
// Sink-only (no initial response)
42+
service DataService {
43+
sink<i32, i32> SinkOnly();
44+
}
45+
46+
// Response and sink (initial response followed by sink)
47+
service FileService {
48+
i32 /* upload id */, sink<binary /* chunk */, i64 /* checksum */> UploadFileChunks(1: string fileName);
49+
}
50+
51+
// Sink with declared exception on sink elements
52+
service ProcessingService {
53+
sink<Data throws (1: ProcessingException ex), Result> ProcessData();
54+
}
55+
56+
// Sink with declared exception on final response
57+
service ValidationService {
58+
sink<Data, Result throws (1: ValidationException ex)> ValidateData();
59+
}
60+
```
61+
62+
## Client-Side Usage
63+
64+
### API Signature
65+
66+
The generated client API returns a callback function that accepts an `iter.Seq2[ElemType, error]`
67+
iterator (the sink producer) and returns the final response. The values returned
68+
depend on whether the sink has an initial response:
69+
70+
**Sink-only (no initial response):**
71+
72+
```go
73+
func (c *Client) SinkMethod(ctx context.Context, args...) (
74+
func(iter.Seq2[ElemType, error]) (FinalResponse, error), // Sink callback
75+
error, // Initial error
76+
)
77+
```
78+
79+
**Response and sink (with initial response):**
80+
81+
```go
82+
func (c *Client) SinkMethod(ctx context.Context, args...) (
83+
*InitialResponse, // First response
84+
func(iter.Seq2[ElemType, error]) (FinalResponse, error), // Sink callback
85+
error, // Initial error
86+
)
87+
```
88+
89+
### Usage Guidelines
90+
91+
1. **Context is Required**: The API REQUIRES a context with a timeout, deadline,
92+
or manual cancel to ensure background goroutines are terminated (avoid
93+
leaks).
94+
95+
2. **Check Initial Error**: Always check the initial error first. If non-nil, no
96+
sink follows.
97+
98+
3. **Create a Producer Function**: Define an `iter.Seq2[ElemType, error]`
99+
function that yields elements to send to the server.
100+
101+
4. **Call the Sink Callback**: Pass your producer function to the sink callback
102+
to start streaming elements to the server.
103+
104+
5. **Handle Final Response**: The sink callback returns the final response from
105+
the server after all elements have been consumed.
106+
107+
6. **Cleanup**: You should NOT worry about cleaning up resources besides
108+
providing a reasonable context timeout/cancellation.
109+
110+
### Example: Sink-Only
111+
112+
```go
113+
func main() {
114+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
115+
defer cancel()
116+
117+
sinkCallback, err := client.SinkOnly(ctx)
118+
if err != nil {
119+
log.Fatalf("request failed: %v", err)
120+
}
121+
122+
// Define the producer function that yields elements
123+
sinkSeq := func(yield func(int32, error) bool) {
124+
for i := int32(1); i <= 5; i++ {
125+
if !yield(i, nil) {
126+
return // Stop if consumer signals to stop
127+
}
128+
}
129+
}
130+
131+
// Call the sink callback with the producer
132+
finalResponse, err := sinkCallback(sinkSeq)
133+
if err != nil {
134+
log.Fatalf("sink failed: %v", err)
135+
}
136+
137+
fmt.Printf("Final response: %d\n", finalResponse)
138+
}
139+
```
140+
141+
### Example: Response and Sink
142+
143+
```go
144+
func main() {
145+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
146+
defer cancel()
147+
148+
uploadID, sinkCallback, err := client.UploadFileChunks(ctx, "myfile.txt")
149+
if err != nil {
150+
log.Fatalf("request failed: %v", err)
151+
}
152+
153+
fmt.Printf("Upload ID: %d\n", uploadID)
154+
155+
// Open file and create producer
156+
file, err := os.Open("myfile.txt")
157+
if err != nil {
158+
log.Fatalf("failed to open file: %v", err)
159+
}
160+
defer file.Close()
161+
162+
sinkSeq := func(yield func([]byte, error) bool) {
163+
buffer := make([]byte, 4096)
164+
for {
165+
n, err := file.Read(buffer)
166+
if err == io.EOF {
167+
return // Done reading
168+
}
169+
if err != nil {
170+
yield(nil, err) // Signal error to server
171+
return
172+
}
173+
if !yield(buffer[:n], nil) {
174+
return // Stop if consumer signals to stop
175+
}
176+
}
177+
}
178+
179+
checksum, err := sinkCallback(sinkSeq)
180+
if err != nil {
181+
log.Fatalf("upload failed: %v", err)
182+
}
183+
184+
fmt.Printf("Upload complete. Checksum: %d\n", checksum)
185+
}
186+
```
187+
188+
### Example: Handling Errors in Producer
189+
190+
```go
191+
sinkSeq := func(yield func(Data, error) bool) {
192+
for _, item := range items {
193+
data, err := processItem(item)
194+
if err != nil {
195+
// Signal error to server - this will be received
196+
// as the error in the server's iter.Seq2 loop
197+
yield(Data{}, err)
198+
return
199+
}
200+
if !yield(data, nil) {
201+
return
202+
}
203+
}
204+
}
205+
```
206+
207+
## Server-Side Implementation
208+
209+
### API Signature
210+
211+
Server-side sink methods have a different signature that returns a consumer
212+
function:
213+
214+
**Sink-only (no initial response):**
215+
216+
```go
217+
func (h *Handler) SinkMethod(ctx context.Context, args...) (
218+
func(context.Context, iter.Seq2[ElemType, error]) (FinalResponse, error), // Consumer function
219+
error, // Initial error
220+
)
221+
```
222+
223+
**Response and sink (with initial response):**
224+
225+
```go
226+
func (h *Handler) SinkMethod(ctx context.Context, args...) (
227+
*InitialResponse, // First response
228+
func(context.Context, iter.Seq2[ElemType, error]) (FinalResponse, error), // Consumer function
229+
error, // Initial error
230+
)
231+
```
232+
233+
### Implementation Guidelines
234+
235+
1. **Return Initial Response/Error First**: Return the initial response and/or
236+
error before sink processing begins.
237+
238+
2. **Consumer Function**: Return a consumer function that will be invoked by the
239+
Thrift library to process sink elements.
240+
241+
3. **Use Context for Cancellation**: The context passed to the consumer function
242+
should be used to detect stream interruption (e.g. client disconnected).
243+
244+
4. **Iterate Over Elements**: Use Go's `for elem, err := range seq` syntax to
245+
consume elements from the client.
246+
247+
5. **Handle Client Errors**: Check the error value in each iteration. If non-nil,
248+
the client encountered an error and stopped sending.
249+
250+
6. **Return Final Response**: After consuming all elements, return the final
251+
response to the client.
252+
253+
7. **Return Error for Sink Errors**: Return an error from the consumer function
254+
to signal a final response error to the client.
255+
256+
8. **Cleanup Resources**: The consumer function should clean up all resources
257+
before returning.
258+
259+
### Example: Sink-Only
260+
261+
```go
262+
type DataService struct{}
263+
264+
func (s *DataService) SinkOnly(ctx context.Context) (func(context.Context, iter.Seq2[int32, error]) (int32, error), error) {
265+
// Return consumer function
266+
elemConsumerFunc := func(ctx context.Context, seq iter.Seq2[int32, error]) (int32, error) {
267+
var sum int32
268+
for elem, err := range seq {
269+
// Check for client-side error
270+
if err != nil {
271+
return sum, err
272+
}
273+
// Check for cancellation
274+
select {
275+
case <-ctx.Done():
276+
return sum, ctx.Err()
277+
default:
278+
}
279+
// Process element
280+
sum += elem
281+
}
282+
return sum, nil // Return final response
283+
}
284+
285+
return elemConsumerFunc, nil
286+
}
287+
```
288+
289+
### Example: Response and Sink
290+
291+
```go
292+
type FileService struct{}
293+
294+
func (h *FileService) UploadFileChunks(ctx context.Context, fileName string) (
295+
int32,
296+
func(context.Context, iter.Seq2[[]byte, error]) (int64, error),
297+
error,
298+
) {
299+
// Create upload and get ID for initial response
300+
uploadID, err := createUpload(fileName)
301+
if err != nil {
302+
return 0, nil, fmt.Errorf("failed to create upload: %w", err)
303+
}
304+
305+
// Create consumer function
306+
elemConsumerFunc := func(ctx context.Context, seq iter.Seq2[[]byte, error]) (int64, error) {
307+
var checksum int64
308+
309+
for chunk, err := range seq {
310+
// Check for client-side error
311+
if err != nil {
312+
cancelUpload(uploadID)
313+
return 0, err
314+
}
315+
316+
// Check for cancellation
317+
select {
318+
case <-ctx.Done():
319+
cancelUpload(uploadID)
320+
return 0, ctx.Err()
321+
default:
322+
}
323+
324+
// Process chunk
325+
if err := writeChunk(uploadID, chunk); err != nil {
326+
cancelUpload(uploadID)
327+
return 0, fmt.Errorf("failed to write chunk: %w", err)
328+
}
329+
checksum = computeChecksum(checksum, chunk)
330+
}
331+
332+
// Finalize upload
333+
if err := finalizeUpload(uploadID); err != nil {
334+
return 0, fmt.Errorf("failed to finalize upload: %w", err)
335+
}
336+
337+
return checksum, nil
338+
}
339+
340+
return uploadID, elemConsumerFunc, nil
341+
}
342+
```
343+
344+
### Example: Returning a Final Response Exception
345+
346+
```go
347+
func (h *Handler) ValidateData(ctx context.Context) (func(context.Context, iter.Seq2[*Data, error]) (*Result, error), error) {
348+
elemConsumerFunc := func(ctx context.Context, seq iter.Seq2[*Data, error]) (*Result, error) {
349+
for data, err := range seq {
350+
if err != nil {
351+
return nil, err
352+
}
353+
if !isValid(data) {
354+
// Return declared exception as final response error
355+
return nil, NewValidationException().SetMessage("invalid data")
356+
}
357+
}
358+
return &Result{Success: true}, nil
359+
}
360+
361+
return elemConsumerFunc, nil
362+
}
363+
```
364+
365+
### Example: Handling Initial Exception
366+
367+
```go
368+
func (h *Handler) ProcessData(ctx context.Context) (func(context.Context, iter.Seq2[*Data, error]) (*Result, error), error) {
369+
if !h.isReady() {
370+
// Return initial error - sink will not proceed
371+
return nil, NewServiceException().SetMessage("service not ready")
372+
}
373+
374+
elemConsumerFunc := func(ctx context.Context, seq iter.Seq2[*Data, error]) (*Result, error) {
375+
// Process elements...
376+
for data, err := range seq {
377+
if err != nil {
378+
return nil, err
379+
}
380+
process(data)
381+
}
382+
return &Result{Success: true}, nil
383+
}
384+
385+
return elemConsumerFunc, nil
386+
}
387+
```
388+
389+
## Additional Resources
390+
391+
- [General Thrift Streaming Documentation](https://www.internalfb.com/intern/staticdocs/thrift/docs/fb/features/streaming/)
392+
- [Thrift Streaming in Go](streaming.md)

0 commit comments

Comments
 (0)