88 "math/rand"
99 "net"
1010 "regexp"
11+ "strconv"
1112 "strings"
1213 "sync"
1314 "sync/atomic"
@@ -23,6 +24,7 @@ import (
2324
2425 "github.com/singlestore-labs/events/eventmodels"
2526 "github.com/singlestore-labs/events/internal"
27+ "github.com/singlestore-labs/events/internal/multi"
2628 "github.com/singlestore-labs/events/internal/pwork"
2729)
2830
@@ -94,6 +96,20 @@ type Library[ID eventmodels.AbstractID[ID], TX eventmodels.AbstractTX, DB eventm
9496}
9597
9698type LibraryNoDB struct {
99+ // --- Size cap subsystem fields (producer-only, decoupled from topic creation) ---
100+ // Global broker caps
101+ sizeCapBrokerLock sync.Mutex // protects changes to sizeCapBrokerState
102+ sizeCapBrokerState atomic.Int32
103+ sizeCapBrokerLoadCtx * multi.Context
104+ sizeCapBrokerReady chan struct {}
105+ sizeCapDefaultAssumed int64 // anything smaller than this can be sent before knowing actual limits
106+ sizeCapBrokerMessageMax atomic.Int64 // message.max.bytes (0 unknown)
107+ sizeCapSocketRequestMax atomic.Int64 // socket.request.max.bytes (rarely limiting; 0 unknown)
108+ sizeCapWork pwork.Work [string , string ] // un-prefixed in APIs
109+ sizeCapTopicLimits gwrap.SyncMap [string , sizeCapTopicLimit ] // un-prefixed topic
110+
111+ // Per-topic limits map (separate from creatingTopic). Keys are topic names.
112+
97113 hasDB atomic.Bool
98114 tracer eventmodels.Tracer
99115 brokers []string
@@ -236,9 +252,13 @@ func New[ID eventmodels.AbstractID[ID], TX eventmodels.AbstractTX, DB eventmodel
236252 doEnhance : true ,
237253 instanceID : instanceCount .Add (1 ),
238254 topicsHaveBeenListed : make (chan struct {}),
255+ sizeCapBrokerReady : make (chan struct {}),
256+ sizeCapDefaultAssumed : 1_000_000 ,
257+ sizeCapBrokerLoadCtx : multi .New (),
239258 },
240259 }
241260 lib .configureTopicsPrework ()
261+ lib .configureSizeCapPrework ()
242262 return & lib
243263}
244264
@@ -327,6 +347,14 @@ func (lib *Library[ID, TX, DB]) SkipNotifierSupport() {
327347 lib .skipNotifier = true
328348}
329349
350+ // SetSizeCapLowerLimit overrides the default lower limit on sizes: any message under
351+ // this size can be sent before actual limits are known.
352+ func (lib * Library [ID , TX , DB ]) SetSizeCapLowerLimit (sizeCapDefaultAssumed int64 ) {
353+ lib .lock .Lock ()
354+ defer lib .lock .Unlock ()
355+ lib .sizeCapDefaultAssumed = sizeCapDefaultAssumed
356+ }
357+
330358// Configure sets up the Library so that it has the configuration it needs to run.
331359// The database connection is optional. Without it, certain features will always error:
332360//
@@ -379,14 +407,14 @@ func (lib *Library[ID, TX, DB]) start(str string, args ...any) error {
379407 case isRunning :
380408 return nil
381409 }
410+ if len (lib .brokers ) == 0 || lib .brokers [0 ] == "" {
411+ return errors .Errorf ("no brokers configured" )
412+ }
382413 lib .writer = kafka .NewWriter (kafka.WriterConfig {
383414 Brokers : lib .brokers ,
384415 Dialer : lib .dialer (),
385416 })
386417 lib .ready .Store (isRunning )
387- if len (lib .brokers ) == 0 || lib .brokers [0 ] == "" {
388- return errors .Errorf ("no brokers configured" )
389- }
390418 return nil
391419}
392420
@@ -650,6 +678,32 @@ func (lib *Library[ID, TX, DB]) Tracer() eventmodels.Tracer { return lib
650678// getController returns a client talking to the controlling broker. The
651679// controller is needed for certain requests, like creating a topic
652680func (lib * LibraryNoDB ) getController (ctx context.Context ) (_ * kafka.Client , err error ) {
681+ var c * kafka.Client
682+ err = lib .findABroker (ctx , func (conn * kafka.Conn ) error {
683+ controller , err := conn .Controller ()
684+ if err != nil {
685+ return errors .Errorf ("event library get controller from kafka connection: %w" , err )
686+ }
687+ ips , err := net .LookupIP (controller .Host )
688+ if err != nil {
689+ return errors .Errorf ("event library lookup IP of controller (%s): %w" , controller .Host , err )
690+ }
691+ if len (ips ) == 0 {
692+ return errors .Errorf ("event library lookup IP of controller (%s) got no addresses" , controller .Host )
693+ }
694+ c = & kafka.Client {
695+ Addr : & net.TCPAddr {
696+ IP : ips [0 ],
697+ Port : controller .Port ,
698+ },
699+ Transport : lib .transport (),
700+ }
701+ return nil
702+ })
703+ return c , err
704+ }
705+
706+ func (lib * LibraryNoDB ) findABroker (ctx context.Context , f func (* kafka.Conn ) error ) (err error ) {
653707 dialer := lib .dialer ()
654708 var tried int
655709 for _ , i := range rand .Perm (len (lib .brokers )) {
@@ -660,37 +714,41 @@ func (lib *LibraryNoDB) getController(ctx context.Context) (_ *kafka.Client, err
660714 lib .tracer .Logf ("[events] could not connect to broker %d (of %d) %s: %v" , i + 1 , len (lib .brokers ), broker , err )
661715 if tried == len (lib .brokers ) {
662716 // last broker, give up
663- return nil , errors .Errorf ("event library dial kafka broker (%s): %w" , broker , err )
717+ return errors .Errorf ("event library dial kafka broker (%s): %w" , broker , err )
664718 }
665719 continue
666720 }
667721 defer func () {
668722 e := conn .Close ()
669723 if err == nil && e != nil {
670- err = errors .Errorf ("event library close dialer (%s): %w" , lib . brokers [ 0 ], err )
724+ err = errors .Errorf ("event library close dialer (%s): %w" , broker , e )
671725 }
672726 }()
673- controller , err := conn .Controller ()
674- if err != nil {
675- return nil , errors .Errorf ("event library get controller from kafka connection: %w" , err )
676- }
677- ips , err := net .LookupIP (controller .Host )
678- if err != nil {
679- return nil , errors .Errorf ("event library lookup IP of controller (%s): %w" , controller .Host , err )
680- }
681- if len (ips ) == 0 {
682- return nil , errors .Errorf ("event library lookup IP of controller (%s) got no addresses" , controller .Host )
683- }
684- return & kafka.Client {
685- Addr : & net.TCPAddr {
686- IP : ips [0 ],
687- Port : controller .Port ,
688- },
689- Transport : lib .transport (),
690- }, nil
727+ return f (conn )
728+ }
729+ return errors .Errorf ("unexpected condition" )
730+ }
731+
732+ func (lib * LibraryNoDB ) getBrokers (ctx context.Context ) ([]kafka.Broker , error ) {
733+ var brokers []kafka.Broker
734+ err := lib .findABroker (ctx , func (conn * kafka.Conn ) error {
735+ var err error
736+ brokers , err = conn .Brokers ()
737+ return err
738+ })
739+ return brokers , err
740+ }
741+
742+ func (lib * LibraryNoDB ) getABrokerID (ctx context.Context ) (string , error ) {
743+ brokers , err := lib .getBrokers (ctx )
744+ if err != nil {
745+ return "" , errors .WithStack (err )
746+ }
747+ if len (brokers ) == 0 {
748+ return "" , errors .Errorf ("get brokers request returned no brokers" )
691749 }
692- // should not be able to get here
693- return nil , errors . Errorf ( "unexpected condition" )
750+ broker := brokers [ rand . Intn ( len ( brokers ))]
751+ return strconv . Itoa ( broker . ID ), nil
694752}
695753
696754// getConsumerGroupCoordinator returns a client talking to the control group's
0 commit comments