package abono import ( "context" "sync" ) // subLocks serialises the work on one Stripe subscription across every // entry point that fetches it and writes what it read: the webhook, // Finish, Reconcile and Adopt (ruling R35). Each holds the subscription's // lock from before its fetch until its write has committed, so the // snapshots are written in the order they were read and an older one can // never overwrite a newer one. SQLite's single writer orders the writes, // not the fetches, which is why the database alone is not enough. // // The lock is in this process: a rastrillo app is one process on one // SQLite file. Several processes writing one database would need a check // in the database instead. type subLocks struct { mu sync.Mutex held map[string]*subLock } type subLock struct { ch chan struct{} // a token while held users int // holders and waiters, so an idle entry is dropped } // lock waits for id's lock and returns the function that releases it, or // ctx's error if ctx ends first. func (l *subLocks) lock(ctx context.Context, id string) (unlock func(), err error) { l.mu.Lock() if l.held == nil { l.held = map[string]*subLock{} } e := l.held[id] if e == nil { e = &subLock{ch: make(chan struct{}, 1)} l.held[id] = e } e.users++ l.mu.Unlock() done := func() { l.mu.Lock() if e.users--; e.users == 0 { delete(l.held, id) } l.mu.Unlock() } select { case e.ch <- struct{}{}: return func() { <-e.ch; done() }, nil case <-ctx.Done(): done() return nil, ctx.Err() } }