···44 "context"55 "fmt"66 "log/slog"77+ "os"88+ "os/signal"79 "sync"1010+ "syscall"811 "time"9121013 "github.com/bluesky-social/jetstream/pkg/client"···1916type DB interface {2017 GetLastTimeUs() (int64, error)2118 SaveLastTimeUs(int64) error2222- UpdateLastTimeUs(int64) error2319}2020+2121+type Set[T comparable] map[T]struct{}24222523type JetstreamClient struct {2624 cfg *client.ClientConfig···2925 ident string3026 l *slog.Logger31272828+ wantedDids Set[string]3229 db DB3330 waitForDid bool3431 mu sync.RWMutex···4237 if did == "" {4338 return4439 }4040+4541 j.mu.Lock()4646- j.cfg.WantedDids = append(j.cfg.WantedDids, did)4242+ j.wantedDids[did] = struct{}{}4743 j.mu.Unlock()4844}49455050-func (j *JetstreamClient) UpdateDids(dids []string) {5151- j.mu.Lock()5252- for _, did := range dids {5353- if did != "" {5454- j.cfg.WantedDids = append(j.cfg.WantedDids, did)4646+type processor func(context.Context, *models.Event) error4747+4848+func (j *JetstreamClient) withDidFilter(processFunc processor) processor {4949+ // since this closure references j.WantedDids; it should auto-update5050+ // existing instances of the closure when j.WantedDids is mutated5151+ return func(ctx context.Context, evt *models.Event) error {5252+ if _, ok := j.wantedDids[evt.Did]; ok {5353+ return processFunc(ctx, evt)5454+ } else {5555+ return nil5556 }5657 }5757- j.mu.Unlock()5858-5959- j.cancelMu.Lock()6060- if j.cancel != nil {6161- j.cancel()6262- }6363- j.cancelMu.Unlock()6458}65596660func NewJetstreamClient(endpoint, ident string, collections []string, cfg *client.ClientConfig, logger *slog.Logger, db DB, waitForDid bool) (*JetstreamClient, error) {···7066 }71677268 return &JetstreamClient{7373- cfg: cfg,7474- ident: ident,7575- db: db,7676- l: logger,6969+ cfg: cfg,7070+ ident: ident,7171+ db: db,7272+ l: logger,7373+ wantedDids: make(map[string]struct{}),77747875 // This will make the goroutine in StartJetstream wait until7979- // cfg.WantedDids has been populated, typically using UpdateDids.7676+ // j.wantedDids has been populated, typically using addDids.8077 waitForDid: waitForDid,8178 }, nil8279}83808481// StartJetstream starts the jetstream client and processes events using the provided processFunc.8585-// The caller is responsible for saving the last time_us to the database (just use your db.SaveLastTimeUs).8282+// The caller is responsible for saving the last time_us to the database (just use your db.UpdateLastTimeUs).8683func (j *JetstreamClient) StartJetstream(ctx context.Context, processFunc func(context.Context, *models.Event) error) error {8784 logger := j.l88858989- sched := sequential.NewScheduler(j.ident, logger, processFunc)8686+ sched := sequential.NewScheduler(j.ident, logger, j.withDidFilter(processFunc))90879188 client, err := client.NewClient(j.cfg, log.New("jetstream"), sched)9289 if err != nil {···97929893 go func() {9994 if j.waitForDid {100100- for len(j.cfg.WantedDids) == 0 {9595+ for len(j.wantedDids) == 0 {10196 time.Sleep(time.Second)10297 }10398 }10499 logger.Info("done waiting for did")100100+101101+ go j.periodicLastTimeSave(ctx)102102+ j.saveIfKilled(ctx)103103+105104 j.connectAndRead(ctx)106105 }()107106···139130 }140131}141132133133+// save cursor periodically134134+func (j *JetstreamClient) periodicLastTimeSave(ctx context.Context) {135135+ ticker := time.NewTicker(time.Minute)136136+ defer ticker.Stop()137137+138138+ for {139139+ select {140140+ case <-ctx.Done():141141+ return142142+ case <-ticker.C:143143+ j.db.SaveLastTimeUs(time.Now().UnixMicro())144144+ }145145+ }146146+}147147+142148func (j *JetstreamClient) getLastTimeUs(ctx context.Context) *int64 {143149 l := log.FromContext(ctx)144150 lastTimeUs, err := j.db.GetLastTimeUs()···166142 }167143 }168144169169- // If last time is older than a week, start from now145145+ // If last time is older than 2 days, start from now170146 if time.Now().UnixMicro()-lastTimeUs > 2*24*60*60*1000*1000 {171147 lastTimeUs = time.Now().UnixMicro()172148 l.Warn("last time us is older than 2 days; discarding that and starting from now")173173- err = j.db.UpdateLastTimeUs(lastTimeUs)149149+ err = j.db.SaveLastTimeUs(lastTimeUs)174150 if err != nil {175151 l.Error("failed to save last time us", "error", err)176152 }···178154179155 l.Info("found last time_us", "time_us", lastTimeUs)180156 return &lastTimeUs157157+}158158+159159+func (j *JetstreamClient) saveIfKilled(ctx context.Context) context.Context {160160+ ctxWithCancel, cancel := context.WithCancel(ctx)161161+162162+ sigChan := make(chan os.Signal, 1)163163+164164+ signal.Notify(sigChan,165165+ syscall.SIGINT,166166+ syscall.SIGTERM,167167+ syscall.SIGQUIT,168168+ syscall.SIGHUP,169169+ syscall.SIGKILL,170170+ syscall.SIGSTOP,171171+ )172172+173173+ go func() {174174+ sig := <-sigChan175175+ j.l.Info("Received signal, initiating graceful shutdown", "signal", sig)176176+177177+ lastTimeUs := time.Now().UnixMicro()178178+ if err := j.db.SaveLastTimeUs(lastTimeUs); err != nil {179179+ j.l.Error("Failed to save last time during shutdown", "error", err)180180+ }181181+ j.l.Info("Saved lastTimeUs before shutdown", "lastTimeUs", lastTimeUs)182182+183183+ j.cancelMu.Lock()184184+ if j.cancel != nil {185185+ j.cancel()186186+ }187187+ j.cancelMu.Unlock()188188+189189+ cancel()190190+191191+ os.Exit(0)192192+ }()193193+194194+ return ctxWithCancel181195}
+6-10
knotserver/db/jetstream.go
···11package db2233func (d *DB) SaveLastTimeUs(lastTimeUs int64) error {44- _, err := d.db.Exec(`insert into _jetstream (last_time_us) values (?)`, lastTimeUs)44+ _, err := d.db.Exec(`55+ insert into _jetstream (id, last_time_us)66+ values (1, ?)77+ on conflict(id) do update set last_time_us = excluded.last_time_us88+ `, lastTimeUs)59 return err66-}77-88-func (d *DB) UpdateLastTimeUs(lastTimeUs int64) error {99- _, err := d.db.Exec(`update _jetstream set last_time_us = ? where rowid = 1`, lastTimeUs)1010- if err != nil {1111- return err1212- }1313- return nil1410}15111612func (d *DB) GetLastTimeUs() (int64, error) {1713 var lastTimeUs int641818- row := d.db.QueryRow(`select last_time_us from _jetstream`)1414+ row := d.db.QueryRow(`select last_time_us from _jetstream where id = 1;`)1915 err := row.Scan(&lastTimeUs)2016 return lastTimeUs, err2117}