Skip to content

Commit b59f008

Browse files
authored
Merge pull request #1214 Writer flush messages before close from ydb-platform/flush-writer-on-close
2 parents 43d8e33 + 859a140 commit b59f008

File tree

7 files changed

+165
-61
lines changed

7 files changed

+165
-61
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
* Added flush messages from buffer before close topic writer
12
* Added Flush method for topic writer
23

34
## v3.66.0

internal/topic/topicwriterinternal/queue.go

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,13 @@ type messageQueue struct {
3636
closedErr error
3737
acksReceivedEvent xsync.EventBroadcast
3838

39-
m xsync.RWMutex
40-
closed bool
41-
closedChan empty.Chan
42-
lastWrittenIndex int
43-
lastSentIndex int
44-
lastSeqNo int64
39+
m xsync.RWMutex
40+
stopReceiveMessagesReason error
41+
closed bool
42+
closedChan empty.Chan
43+
lastWrittenIndex int
44+
lastSentIndex int
45+
lastSeqNo int64
4546

4647
messagesByOrder map[int]messageWithDataContent
4748
seqNoToOrderID map[int64]int
@@ -77,8 +78,10 @@ func (q *messageQueue) addMessages(messages []messageWithDataContent, needWaiter
7778
q.m.Lock()
7879
defer q.m.Unlock()
7980

80-
if q.closed {
81-
return waiter, xerrors.WithStackTrace(fmt.Errorf("ydb: add message to closed message queue: %w", q.closedErr))
81+
if q.stopReceiveMessagesReason != nil {
82+
return waiter, xerrors.WithStackTrace(
83+
fmt.Errorf("ydb: add message to closed message queue: %w", q.stopReceiveMessagesReason),
84+
)
8285
}
8386

8487
if err := q.checkNewMessagesBeforeAddNeedLock(messages); err != nil {
@@ -181,6 +184,19 @@ func (q *messageQueue) ackReceivedNeedLock(seqNo int64) error {
181184
return nil
182185
}
183186

187+
func (q *messageQueue) StopAddNewMessages(reason error) {
188+
q.m.Lock()
189+
defer q.m.Unlock()
190+
191+
q.stopAddNewMessagesNeedLock(reason)
192+
}
193+
194+
func (q *messageQueue) stopAddNewMessagesNeedLock(reason error) {
195+
if q.stopReceiveMessagesReason == nil {
196+
q.stopReceiveMessagesReason = reason
197+
}
198+
}
199+
184200
func (q *messageQueue) Close(err error) error {
185201
isFirstTimeClosed := false
186202
q.m.Lock()
@@ -193,6 +209,8 @@ func (q *messageQueue) Close(err error) error {
193209
}
194210
}()
195211

212+
q.stopAddNewMessagesNeedLock(err)
213+
196214
if q.closed {
197215
return xerrors.WithStackTrace(errCloseClosedMessageQueue)
198216
}

internal/topic/topicwriterinternal/writer_reconnector.go

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,17 @@ func (w *WriterReconnector) Flush(ctx context.Context) error {
330330
}
331331

332332
func (w *WriterReconnector) Close(ctx context.Context) error {
333-
return w.close(ctx, xerrors.WithStackTrace(errStopWriterReconnector))
333+
reason := xerrors.WithStackTrace(errStopWriterReconnector)
334+
w.queue.StopAddNewMessages(reason)
335+
336+
flushErr := w.Flush(ctx)
337+
closeErr := w.close(ctx, reason)
338+
339+
if flushErr != nil {
340+
return flushErr
341+
}
342+
343+
return closeErr
334344
}
335345

336346
func (w *WriterReconnector) close(ctx context.Context, reason error) (resErr error) {
@@ -339,9 +349,13 @@ func (w *WriterReconnector) close(ctx context.Context, reason error) (resErr err
339349
onDone(resErr)
340350
}()
341351

342-
resErr = w.queue.Close(reason)
352+
closeErr := w.queue.Close(reason)
353+
if resErr == nil && closeErr != nil {
354+
resErr = closeErr
355+
}
356+
343357
bgErr := w.background.Close(ctx, reason)
344-
if resErr == nil {
358+
if resErr == nil && bgErr != nil {
345359
resErr = bgErr
346360
}
347361

internal/topic/topicwriterinternal/writer_reconnector_test.go

Lines changed: 90 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,8 @@ func TestWriterImpl_WriteCodecs(t *testing.T) {
196196
Data: bytes.NewReader(messContent),
197197
}}))
198198

199-
require.Equal(t, rawtopiccommon.CodecRaw, <-messReceived)
199+
mess := <-messReceived
200+
require.Equal(t, rawtopiccommon.CodecRaw, mess)
200201
})
201202
t.Run("ForceGzip", func(t *testing.T) {
202203
var err error
@@ -531,67 +532,107 @@ func TestWriterImpl_Reconnect(t *testing.T) {
531532
}
532533

533534
func TestWriterImpl_CloseWithFlush(t *testing.T) {
534-
e := newTestEnv(t, nil)
535+
type flushMethod func(ctx context.Context, writer *WriterReconnector) error
535536

536-
messageTime := time.Date(2023, 9, 7, 11, 34, 0, 0, time.UTC)
537-
messageData := []byte("123")
537+
f := func(t testing.TB, flush flushMethod) {
538+
e := newTestEnv(t, nil)
538539

539-
const seqNo = 36
540+
messageTime := time.Date(2023, 9, 7, 11, 34, 0, 0, time.UTC)
541+
messageData := []byte("123")
540542

541-
writeCompleted := make(empty.Chan)
542-
e.stream.EXPECT().Send(&rawtopicwriter.WriteRequest{
543-
Messages: []rawtopicwriter.MessageData{
544-
{
545-
SeqNo: seqNo,
546-
CreatedAt: messageTime,
547-
UncompressedSize: int64(len(messageData)),
548-
Partitioning: rawtopicwriter.Partitioning{},
549-
Data: messageData,
543+
const seqNo = 36
544+
545+
writeCompleted := make(empty.Chan)
546+
e.stream.EXPECT().Send(&rawtopicwriter.WriteRequest{
547+
Messages: []rawtopicwriter.MessageData{
548+
{
549+
SeqNo: seqNo,
550+
CreatedAt: messageTime,
551+
UncompressedSize: int64(len(messageData)),
552+
Partitioning: rawtopicwriter.Partitioning{},
553+
Data: messageData,
554+
},
550555
},
551-
},
552-
Codec: rawtopiccommon.CodecRaw,
553-
}).Return(nil)
556+
Codec: rawtopiccommon.CodecRaw,
557+
}).Return(nil)
554558

555-
closeCompleted := make(empty.Chan)
556-
go func() {
557-
err := e.writer.Write(e.ctx, []PublicMessage{{
558-
SeqNo: seqNo,
559-
CreatedAt: messageTime,
560-
Data: bytes.NewReader(messageData),
561-
}})
562-
close(writeCompleted)
563-
require.NoError(t, err)
564-
}()
559+
flushCompleted := make(empty.Chan)
560+
go func() {
561+
err := e.writer.Write(e.ctx, []PublicMessage{{
562+
SeqNo: seqNo,
563+
CreatedAt: messageTime,
564+
Data: bytes.NewReader(messageData),
565+
}})
566+
close(writeCompleted)
567+
require.NoError(t, err)
568+
}()
565569

566-
<-writeCompleted
570+
<-writeCompleted
567571

568-
go func() {
569-
require.NoError(t, e.writer.Flush(e.ctx))
570-
require.NoError(t, e.writer.Close(e.ctx))
571-
close(closeCompleted)
572-
}()
572+
go func() {
573+
require.NoError(t, flush(e.ctx, e.writer))
574+
close(flushCompleted)
575+
}()
573576

574-
select {
575-
case <-closeCompleted:
576-
t.Fatal("flush and close must complete only after message is acked")
577-
case <-time.After(100 * time.Millisecond):
578-
// pass
579-
}
577+
select {
578+
case <-flushCompleted:
579+
t.Fatal("flush and close must complete only after message is acked")
580+
case <-time.After(10 * time.Millisecond):
581+
// pass
582+
}
580583

581-
e.sendFromServer(&rawtopicwriter.WriteResult{
582-
Acks: []rawtopicwriter.WriteAck{
583-
{
584-
SeqNo: seqNo,
585-
MessageWriteStatus: rawtopicwriter.MessageWriteStatus{
586-
Type: rawtopicwriter.WriteStatusTypeWritten,
587-
WrittenOffset: 4,
584+
e.sendFromServer(&rawtopicwriter.WriteResult{
585+
Acks: []rawtopicwriter.WriteAck{
586+
{
587+
SeqNo: seqNo,
588+
MessageWriteStatus: rawtopicwriter.MessageWriteStatus{
589+
Type: rawtopicwriter.WriteStatusTypeWritten,
590+
WrittenOffset: 4,
591+
},
588592
},
589593
},
594+
PartitionID: e.partitionID,
595+
})
596+
597+
xtest.WaitChannelClosed(t, flushCompleted)
598+
}
599+
600+
tests := []struct {
601+
name string
602+
flush flushMethod
603+
}{
604+
{
605+
name: "close",
606+
flush: func(ctx context.Context, writer *WriterReconnector) error {
607+
return writer.Close(ctx)
608+
},
590609
},
591-
PartitionID: e.partitionID,
592-
})
610+
{
611+
name: "flush",
612+
flush: func(ctx context.Context, writer *WriterReconnector) error {
613+
return writer.Close(ctx)
614+
},
615+
},
616+
{
617+
name: "flush and close",
618+
flush: func(ctx context.Context, writer *WriterReconnector) error {
619+
err := writer.Flush(ctx)
620+
if err != nil {
621+
return err
622+
}
623+
624+
return writer.Close(ctx)
625+
},
626+
},
627+
}
593628

594-
xtest.WaitChannelClosed(t, closeCompleted)
629+
for _, test := range tests {
630+
t.Run(test.name, func(t *testing.T) {
631+
xtest.TestManyTimes(t, func(t testing.TB) {
632+
f(t, test.flush)
633+
})
634+
})
635+
}
595636
}
596637

597638
func TestAllMessagesHasSameBufCodec(t *testing.T) {

tests/integration/helpers_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,9 @@ func (scope *scopeT) Driver(opts ...ydb.Option) *ydb.Driver {
103103
)...,
104104
)
105105
clean := func() {
106-
scope.Require.NoError(driver.Close(scope.Ctx))
106+
if driver != nil {
107+
scope.Require.NoError(driver.Close(scope.Ctx))
108+
}
107109
}
108110

109111
return fixenv.NewGenericResultWithCleanup(driver, clean), err

tests/integration/topic_read_writer_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,32 @@ func TestTopicWriterWithManualPartitionSelect(t *testing.T) {
438438
require.NoError(t, err)
439439
}
440440

441+
func TestWriterFlushMessagesBeforeClose(t *testing.T) {
442+
s := newScope(t)
443+
ctx := s.Ctx
444+
writer, err := s.Driver().Topic().StartWriter(s.TopicPath(), topicoptions.WithWriterWaitServerAck(false))
445+
require.NoError(t, err)
446+
447+
count := 1000
448+
for i := 0; i < count; i++ {
449+
require.NoError(t, writer.Write(ctx, topicwriter.Message{Data: strings.NewReader(strconv.Itoa(i))}))
450+
}
451+
require.NoError(t, writer.Close(ctx))
452+
453+
for i := 0; i < count; i++ {
454+
readCtx, cancel := context.WithTimeout(ctx, time.Second)
455+
mess, err := s.TopicReader().ReadMessage(readCtx)
456+
cancel()
457+
require.NoError(t, err)
458+
459+
messBody, err := io.ReadAll(mess)
460+
require.NoError(t, err)
461+
messBodyString := string(messBody)
462+
require.Equal(t, strconv.Itoa(i), messBodyString)
463+
cancel()
464+
}
465+
}
466+
441467
var topicCounter int
442468

443469
func createTopic(ctx context.Context, t testing.TB, db *ydb.Driver) (topicPath string) {

topic/topicwriter/topicwriter.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ func (w *Writer) WaitInitInfo(ctx context.Context) (info PublicInitialInfo, err
6666
return publicInfo, nil
6767
}
6868

69+
// Close will flush rested messages from buffer and close the writer.
70+
// You can't write new messages after call Close
6971
func (w *Writer) Close(ctx context.Context) error {
7072
return w.inner.Close(ctx)
7173
}

0 commit comments

Comments
 (0)