fix(go): singleflight tdengine child table ensures

This commit is contained in:
lingniu
2026-07-03 19:49:04 +08:00
parent 3a0d9bd840
commit 68b729fa7b
3 changed files with 151 additions and 42 deletions

View File

@@ -4,6 +4,7 @@ import (
"context"
"database/sql"
"strings"
"sync"
"testing"
"time"
@@ -318,6 +319,47 @@ func TestSchemaStatementsCreateRawFramePayloadChunks(t *testing.T) {
}
}
func TestWriterEnsuresSameChildTableOnceUnderConcurrency(t *testing.T) {
exec := newBlockingExec("CREATE TABLE IF NOT EXISTS raw_")
writer := NewWriter(exec)
first := sampleEnvelope()
second := sampleEnvelope()
second.Sequence = 2
second.EventID = "second"
var wg sync.WaitGroup
errs := make(chan error, 2)
wg.Add(2)
go func() {
defer wg.Done()
errs <- writer.AppendAll(context.Background(), first)
}()
<-exec.blocked
go func() {
defer wg.Done()
errs <- writer.AppendAll(context.Background(), second)
}()
time.Sleep(25 * time.Millisecond)
exec.release <- struct{}{}
wg.Wait()
close(errs)
for err := range errs {
if err != nil {
t.Fatalf("AppendAll() error = %v", err)
}
}
if got := exec.count("CREATE TABLE IF NOT EXISTS raw_"); got != 1 {
t.Fatalf("raw child CREATE count = %d, want 1; calls=%#v", got, exec.calls())
}
if got := exec.count("ALTER TABLE raw_"); got != 1 {
t.Fatalf("raw child ALTER count = %d, want 1; calls=%#v", got, exec.calls())
}
if got := exec.count("INSERT INTO raw_"); got != 2 {
t.Fatalf("raw insert count = %d, want 2; calls=%#v", got, exec.calls())
}
}
func sampleEnvelope() envelope.FrameEnvelope {
return envelope.FrameEnvelope{
Protocol: envelope.ProtocolJT808,
@@ -392,3 +434,45 @@ func (e *recordingExec) ExecContext(_ context.Context, query string, args ...any
e.calls = append(e.calls, execCall{query: query, args: args})
return nil, nil
}
type blockingExec struct {
mu sync.Mutex
recorded []execCall
blockPattern string
blocked chan struct{}
release chan struct{}
blockOnce sync.Once
}
func newBlockingExec(blockPattern string) *blockingExec {
return &blockingExec{
blockPattern: blockPattern,
blocked: make(chan struct{}),
release: make(chan struct{}),
}
}
func (e *blockingExec) ExecContext(_ context.Context, query string, args ...any) (sql.Result, error) {
e.mu.Lock()
e.recorded = append(e.recorded, execCall{query: query, args: args})
e.mu.Unlock()
if strings.Contains(query, e.blockPattern) {
e.blockOnce.Do(func() {
close(e.blocked)
<-e.release
})
}
return nil, nil
}
func (e *blockingExec) calls() []execCall {
e.mu.Lock()
defer e.mu.Unlock()
out := make([]execCall, len(e.recorded))
copy(out, e.recorded)
return out
}
func (e *blockingExec) count(pattern string) int {
return countSQL(e.calls(), pattern)
}