RC-5
This commit is contained in:
@@ -100,6 +100,21 @@ func (s *State) ConnectedCount() int64 {
|
||||
return int64(len(s.presence))
|
||||
}
|
||||
|
||||
// ForgetClient removes non-durable runtime state for an identity that was
|
||||
// deleted by the admin profile cleanup tool. The caller must only pass clients
|
||||
// that are currently disconnected.
|
||||
func (s *State) ForgetClient(clientID string) {
|
||||
s.mu.Lock()
|
||||
delete(s.presence, clientID)
|
||||
delete(s.selected, clientID)
|
||||
for k := range s.guesses {
|
||||
if k.ClientID == clientID {
|
||||
delete(s.guesses, k)
|
||||
}
|
||||
}
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
func (s *State) SetTaskSelection(clientID, taskID string) {
|
||||
s.mu.Lock()
|
||||
if taskID == "" {
|
||||
@@ -161,6 +176,57 @@ type AcceptResult struct {
|
||||
Improved bool
|
||||
}
|
||||
|
||||
// CanSubmit validates sequence and per-client timing before a request is
|
||||
// admitted into the global-per-task lottery. It does not mutate state, so an
|
||||
// unselected ticket can be consumed explicitly by SkipLottery.
|
||||
func (s *State) CanSubmit(task data.Task, cid string, seq int64, minInterval time.Duration) error {
|
||||
k := guessKey{task.ID, cid}
|
||||
now := time.Now().UTC()
|
||||
s.mu.RLock()
|
||||
g, ok := s.guesses[k]
|
||||
s.mu.RUnlock()
|
||||
if !ok || g.PublicSeed != task.PublicSeed || seq != g.NextSeq {
|
||||
s.record(false, false, true)
|
||||
return ErrBadSequence
|
||||
}
|
||||
if !g.LastGuess.IsZero() && minInterval > 0 && now.Sub(g.LastGuess) < minInterval {
|
||||
s.record(false, false, true)
|
||||
return ErrRateLimited
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SkipLottery consumes a valid sequence that lost the random draw without
|
||||
// evaluating/scoring it. This prevents the same deterministic guess from being
|
||||
// resubmitted forever while keeping guess_count reserved for actually accepted
|
||||
// and evaluated tips.
|
||||
func (s *State) SkipLottery(task data.Task, cid string, seq int64, minInterval time.Duration) (int64, error) {
|
||||
k := guessKey{task.ID, cid}
|
||||
now := time.Now().UTC()
|
||||
s.mu.Lock()
|
||||
g, ok := s.guesses[k]
|
||||
if !ok || g.PublicSeed != task.PublicSeed || seq != g.NextSeq {
|
||||
next := g.NextSeq
|
||||
s.mu.Unlock()
|
||||
s.record(false, false, true)
|
||||
return next, ErrBadSequence
|
||||
}
|
||||
if !g.LastGuess.IsZero() && minInterval > 0 && now.Sub(g.LastGuess) < minInterval {
|
||||
next := g.NextSeq
|
||||
s.mu.Unlock()
|
||||
s.record(false, false, true)
|
||||
return next, ErrRateLimited
|
||||
}
|
||||
g.NextSeq++
|
||||
g.LastGuess = now
|
||||
g.Revision = task.Revision
|
||||
s.guesses[k] = g
|
||||
next := g.NextSeq
|
||||
s.mu.Unlock()
|
||||
s.record(false, false, true)
|
||||
return next, nil
|
||||
}
|
||||
|
||||
func (s *State) Accept(task data.Task, cid string, seq int64, score float64, minInterval time.Duration) (AcceptResult, error) {
|
||||
k := guessKey{task.ID, cid}
|
||||
now := time.Now().UTC()
|
||||
|
||||
Reference in New Issue
Block a user