Skip to content

Commit f1d6c4d

Browse files
committed
Polishing
1 parent a147055 commit f1d6c4d

4 files changed

Lines changed: 92 additions & 105 deletions

File tree

decentralized-api/cmd/devshardd/engine.go

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ func (e *devshardEngine) doWithFallbackNodes(
249249
if !ok {
250250
if limit && len(capacityExcluded) > 0 {
251251
// Every known node is at its local bound — wait and retry.
252-
clearMap(capacityExcluded)
252+
clear(capacityExcluded)
253253
select {
254254
case <-ctx.Done():
255255
lastReason = observability.ReasonTimeout
@@ -332,12 +332,6 @@ func mergeExcluded(a, b map[string]struct{}) map[string]struct{} {
332332
return out
333333
}
334334

335-
func clearMap(m map[string]struct{}) {
336-
for k := range m {
337-
delete(m, k)
338-
}
339-
}
340-
341335
// shouldFallback reports whether an Acquire error means dapi is unreachable
342336
// and the passive cache should be used. ResourceExhausted is not a fallback
343337
// trigger — dapi is up and remains authoritative for load balancing.

decentralized-api/internal/event_listener/escrow_events.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,12 @@ func (e *DevshardEscrowCreatedEventHandler) Handle(event *chainevents.JSONRPCRes
4141
if err != nil {
4242
return fmt.Errorf("parse escrow_id: %w", err)
4343
}
44-
epochIndex, _ := strconv.ParseUint(firstAttr(ev, escrowCreatedEvent+".epoch_index"), 10, 64)
44+
epochIndex, err := strconv.ParseUint(firstAttr(ev, escrowCreatedEvent+".epoch_index"), 10, 64)
45+
if err != nil {
46+
logging.Warn("host_events: malformed epoch_index on escrow_created; defaulting to 0", types.EventProcessing,
47+
"escrow_id", escrowID, "raw", firstAttr(ev, escrowCreatedEvent+".epoch_index"), "error", err)
48+
epochIndex = 0
49+
}
4550
payload := &apiconfig.EscrowPayload{
4651
EscrowID: escrowID,
4752
EpochIndex: epochIndex,
@@ -50,7 +55,7 @@ func (e *DevshardEscrowCreatedEventHandler) Handle(event *chainevents.JSONRPCRes
5055
Amount: firstAttr(ev, escrowCreatedEvent+".amount"),
5156
}
5257

53-
hold, ok := el.localHoldsEscrowSlot(fmt.Sprint(escrowID))
58+
hold, ok := el.localHoldsEscrowSlot(strconv.FormatUint(escrowID, 10))
5459
if ok && !hold {
5560
logging.Debug("host_events: skip escrow_created; local node not in slots", types.EventProcessing,
5661
"escrow_id", escrowID)
@@ -93,7 +98,7 @@ func (e *DevshardEscrowSettledEventHandler) Handle(event *chainevents.JSONRPCRes
9398
Remainder: firstAttr(ev, escrowSettledEvent+".remainder"),
9499
}
95100

96-
hold, ok := el.localHoldsEscrowSlot(fmt.Sprint(escrowID))
101+
hold, ok := el.localHoldsEscrowSlot(strconv.FormatUint(escrowID, 10))
97102
if ok && !hold {
98103
logging.Debug("host_events: skip escrow_settled; local node not in slots", types.EventProcessing,
99104
"escrow_id", escrowID)

decentralized-api/nodemanager/host_events_rpc.go

Lines changed: 63 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -30,70 +30,79 @@ func (s *Server) GetHostEvents(ctx context.Context, req *gen.GetHostEventsReques
3030
cursor := req.GetCursor()
3131

3232
for {
33-
var wake <-chan struct{}
34-
var release func()
35-
if maxWait > 0 {
36-
// Subscribe (with the client's kind filter) before Since to avoid
37-
// lost wake-ups. Only subscribed kinds wake this waiter, so an
38-
// unsubscribed kind (e.g. maintenance) cannot reset the deadline.
39-
wake, release = s.hostEvents.Subscribe(subscribe)
33+
resp, err, again := s.getHostEventsOnce(ctx, cursor, clientGen, subscribe, maxWait)
34+
if !again {
35+
return resp, err
4036
}
37+
// Notified by a subscribed kind: loop and re-check Since.
38+
}
39+
}
4140

42-
got := s.hostEvents.Since(cursor, clientGen, subscribe)
43-
if got.Reset {
44-
if release != nil {
45-
release()
46-
}
47-
logging.Info("host_events: GetHostEvents needs_reset", types.Config,
48-
"cursor", cursor,
49-
"clientGeneration", clientGen,
50-
"serverGeneration", got.Generation,
51-
"nextCursor", got.NextCursor,
52-
)
53-
return s.hostEventsResponse(got, nil, false, true), nil
54-
}
55-
if len(got.Events) > 0 {
56-
if release != nil {
57-
release()
58-
}
59-
logging.Debug("host_events: GetHostEvents returning events", types.Config,
60-
"cursor", cursor,
61-
"count", len(got.Events),
62-
"nextCursor", got.NextCursor,
63-
"generation", got.Generation,
64-
)
65-
return s.hostEventsResponse(got, hostEventsToProto(got.Events), false, false), nil
66-
}
41+
// getHostEventsOnce runs one subscribe → Since → wait cycle. release is deferred
42+
// so every exit path (return or loop-continue via again=true) deregisters the waiter.
43+
func (s *Server) getHostEventsOnce(
44+
ctx context.Context,
45+
cursor, clientGen uint64,
46+
subscribe []apiconfig.HostEventKind,
47+
maxWait time.Duration,
48+
) (resp *gen.GetHostEventsResponse, err error, again bool) {
49+
var wake <-chan struct{}
50+
var release func()
51+
if maxWait > 0 {
52+
// Subscribe (with the client's kind filter) before Since to avoid
53+
// lost wake-ups. Only subscribed kinds wake this waiter, so an
54+
// unsubscribed kind (e.g. maintenance) cannot reset the deadline.
55+
wake, release = s.hostEvents.Subscribe(subscribe)
56+
}
57+
if release != nil {
58+
defer release()
59+
}
6760

68-
if maxWait <= 0 {
69-
return s.hostEventsResponse(got, nil, true, false), nil
70-
}
61+
got := s.hostEvents.Since(cursor, clientGen, subscribe)
62+
if got.Reset {
63+
logging.Info("host_events: GetHostEvents needs_reset", types.Config,
64+
"cursor", cursor,
65+
"clientGeneration", clientGen,
66+
"serverGeneration", got.Generation,
67+
"nextCursor", got.NextCursor,
68+
)
69+
return s.hostEventsResponse(got, nil, false, true), nil, false
70+
}
71+
if len(got.Events) > 0 {
72+
logging.Debug("host_events: GetHostEvents returning events", types.Config,
73+
"cursor", cursor,
74+
"count", len(got.Events),
75+
"nextCursor", got.NextCursor,
76+
"generation", got.Generation,
77+
)
78+
return s.hostEventsResponse(got, hostEventsToProto(got.Events), false, false), nil, false
79+
}
80+
81+
if maxWait <= 0 {
82+
return s.hostEventsResponse(got, nil, true, false), nil, false
83+
}
7184

72-
logging.Debug("host_events: GetHostEvents long-poll waiting", types.Config,
85+
logging.Debug("host_events: GetHostEvents long-poll waiting", types.Config,
86+
"cursor", cursor,
87+
"nextCursor", got.NextCursor,
88+
"generation", got.Generation,
89+
"maxWait", maxWait,
90+
)
91+
outcome, waitErr := longpoll.Wait(ctx, wake, maxWait)
92+
if waitErr != nil {
93+
return nil, status.FromContextError(waitErr).Err(), false
94+
}
95+
if outcome == longpoll.TimedOut {
96+
got = s.hostEvents.Since(cursor, clientGen, subscribe)
97+
logging.Debug("host_events: GetHostEvents long-poll timed out", types.Config,
7398
"cursor", cursor,
7499
"nextCursor", got.NextCursor,
75100
"generation", got.Generation,
76101
"maxWait", maxWait,
77102
)
78-
outcome, err := longpoll.Wait(ctx, wake, maxWait)
79-
if release != nil {
80-
release()
81-
}
82-
if err != nil {
83-
return nil, status.FromContextError(err).Err()
84-
}
85-
if outcome == longpoll.TimedOut {
86-
got = s.hostEvents.Since(cursor, clientGen, subscribe)
87-
logging.Debug("host_events: GetHostEvents long-poll timed out", types.Config,
88-
"cursor", cursor,
89-
"nextCursor", got.NextCursor,
90-
"generation", got.Generation,
91-
"maxWait", maxWait,
92-
)
93-
return s.hostEventsResponse(got, nil, true, got.Reset), nil
94-
}
95-
// Notified by a subscribed kind: loop and re-check Since.
103+
return s.hostEventsResponse(got, nil, true, got.Reset), nil, false
96104
}
105+
return nil, nil, true
97106
}
98107

99108
func (s *Server) hostEventsResponse(got apiconfig.HostEventSince, events []*gen.HostEvent, unchanged, needsReset bool) *gen.GetHostEventsResponse {

devshard/mlnode/capacity.go

Lines changed: 20 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,12 @@ type Cache struct {
8888
mu sync.Mutex
8989
nodes map[string]*nodeCap // key: nodeID
9090

91-
// localInFlight is keyed by nodeID\x00model (physical vLLM occupancy).
92-
inFlight map[string]int
91+
// inFlight is keyed by (nodeID, model) — physical vLLM occupancy.
92+
inFlight map[nodeModelKey]int
9393
// unknownInFlight tracks in-flight slots for capacity-unknown fallback nodes
9494
// (not in c.nodes). Kept separate so applyPoll's prune never wipes live
9595
// counts for a node dapi has not (yet) reported.
96-
unknownInFlight map[string]int
96+
unknownInFlight map[nodeModelKey]int
9797

9898
activeLoad ActiveLoadFunc
9999
now func() time.Time
@@ -135,8 +135,8 @@ func NewCache(client CapacityClient, opts CacheOptions) *Cache {
135135
return &Cache{
136136
client: client,
137137
nodes: make(map[string]*nodeCap),
138-
inFlight: make(map[string]int),
139-
unknownInFlight: make(map[string]int),
138+
inFlight: make(map[nodeModelKey]int),
139+
unknownInFlight: make(map[nodeModelKey]int),
140140
activeLoad: active,
141141
now: opts.Now,
142142
log: opts.Log,
@@ -372,8 +372,7 @@ func (c *Cache) TryAcquire(nodeID, model string) bool {
372372
if c.availableSlotsLocked(nodeID, div) <= 0 {
373373
return false
374374
}
375-
key := inFlightKey(nodeID, model)
376-
c.inFlight[key]++
375+
c.inFlight[nodeModelKey{nodeID: nodeID, model: model}]++
377376
return true
378377
}
379378

@@ -422,7 +421,7 @@ func (c *Cache) Release(nodeID, model string) {
422421
}
423422
c.mu.Lock()
424423
defer c.mu.Unlock()
425-
key := inFlightKey(nodeID, model)
424+
key := nodeModelKey{nodeID: nodeID, model: model}
426425
if c.inFlight[key] <= 1 {
427426
delete(c.inFlight, key)
428427
return
@@ -458,10 +457,10 @@ func (c *Cache) TryAcquireUnknown(nodeID, model string) bool {
458457
eff := c.unknownEffectiveMax() // reads load map; must not hold c.mu
459458
c.mu.Lock()
460459
defer c.mu.Unlock()
461-
if unknownInFlightSum(c.unknownInFlight, nodeID) >= eff {
460+
if inFlightSum(c.unknownInFlight, nodeID) >= eff {
462461
return false
463462
}
464-
c.unknownInFlight[inFlightKey(nodeID, model)]++
463+
c.unknownInFlight[nodeModelKey{nodeID: nodeID, model: model}]++
465464
return true
466465
}
467466

@@ -472,60 +471,40 @@ func (c *Cache) ReleaseUnknown(nodeID, model string) {
472471
}
473472
c.mu.Lock()
474473
defer c.mu.Unlock()
475-
key := inFlightKey(nodeID, model)
474+
key := nodeModelKey{nodeID: nodeID, model: model}
476475
if c.unknownInFlight[key] <= 1 {
477476
delete(c.unknownInFlight, key)
478477
return
479478
}
480479
c.unknownInFlight[key]--
481480
}
482481

483-
func unknownInFlightSum(m map[string]int, nodeID string) int {
484-
prefix := nodeID + "\x00"
482+
// nodeModelKey identifies one (node, model) in-flight counter.
483+
type nodeModelKey struct {
484+
nodeID string
485+
model string
486+
}
487+
488+
func inFlightSum(m map[nodeModelKey]int, nodeID string) int {
485489
sum := 0
486490
for k, v := range m {
487-
if len(k) >= len(prefix) && k[:len(prefix)] == prefix {
491+
if k.nodeID == nodeID {
488492
sum += v
489493
}
490494
}
491495
return sum
492496
}
493497

494498
func (c *Cache) inFlightSumLocked(nodeID string) int {
495-
prefix := nodeID + "\x00"
496-
sum := 0
497-
for k, v := range c.inFlight {
498-
if len(k) >= len(prefix) && k[:len(prefix)] == prefix {
499-
sum += v
500-
}
501-
}
502-
return sum
499+
return inFlightSum(c.inFlight, nodeID)
503500
}
504501

505502
func (c *Cache) pruneInFlightLocked() {
506503
for k := range c.inFlight {
507-
nodeID, _, ok := splitInFlightKey(k)
508-
if !ok {
504+
if _, exists := c.nodes[k.nodeID]; !exists {
509505
delete(c.inFlight, k)
510-
continue
511-
}
512-
if _, exists := c.nodes[nodeID]; !exists {
513-
delete(c.inFlight, k)
514-
}
515-
}
516-
}
517-
518-
func inFlightKey(nodeID, model string) string {
519-
return nodeID + "\x00" + model
520-
}
521-
522-
func splitInFlightKey(key string) (nodeID, model string, ok bool) {
523-
for i := 0; i < len(key); i++ {
524-
if key[i] == 0 {
525-
return key[:i], key[i+1:], true
526506
}
527507
}
528-
return "", "", false
529508
}
530509

531510
// ApplyPollForTest applies a successful poll snapshot (tests only).

0 commit comments

Comments
 (0)