feat(logits): add safety for zero copy access#2
Conversation
Greptile OverviewGreptile SummaryThis PR implements a memory safety mechanism for zero-copy logits access using a "Memoized Step-Scoped Views with Explicit Revocation" pattern. The changes prevent use-after-invalidation bugs when JavaScript code holds references to logits buffers that become stale after Key Changes:
Issue Found:
Confidence Score: 3/5
Important Files ChangedFile Analysis
Sequence DiagramsequenceDiagram
participant JS as JavaScript Code
participant Ctx as SessionContext
participant Buf as ArrayBuffer
participant CPP as llama.cpp
Note over JS,CPP: Initial Decode
JS->>Ctx: decode(tokens, position)
Ctx->>Ctx: invalidateLogits()
Note over Ctx: Detach old buffer<br/>_decodeStepId++
Ctx->>CPP: llama_decode()
CPP-->>Ctx: logits written to internal buffer
Ctx-->>JS: success
Note over JS,CPP: First getLogits() Call
JS->>Ctx: getLogits()
Ctx->>Ctx: Check memoization<br/>(_logitsStepId == _decodeStepId?)
Note over Ctx: No existing buffer
Ctx->>CPP: lloyal::logits::get(ctx, -1)
CPP-->>Ctx: float* logits
Ctx->>Buf: Create ArrayBuffer (zero-copy)
Ctx->>Ctx: Store reference<br/>_logitsBufferRef = buffer<br/>_logitsStepId = _decodeStepId
Ctx-->>JS: Float32Array view
Note over JS,CPP: Second getLogits() Call (Same Step)
JS->>Ctx: getLogits()
Ctx->>Ctx: Check memoization<br/>(_logitsStepId == _decodeStepId?)
Note over Ctx: Buffer exists, same step
Ctx-->>JS: Return existing buffer
Note over JS,CPP: Next Decode Invalidates Buffer
JS->>Ctx: decode(nextTokens, position)
Ctx->>Ctx: invalidateLogits()
Ctx->>Buf: Detach ArrayBuffer
Note over Buf: Buffer detached<br/>byteLength = 0
Ctx->>Ctx: _decodeStepId++
Ctx->>CPP: llama_decode()
CPP-->>Ctx: new logits
Ctx-->>JS: success
Note over JS,Buf: Old Buffer Access (Error)
JS->>Buf: Access old logits[0]
Buf-->>JS: TypeError (detached)
|
| Napi::ArrayBuffer buffer = Napi::ArrayBuffer::New(env, logits, n_vocab * sizeof(float)); | ||
|
|
||
| // Store weak reference for memoization | ||
| _logitsBufferRef = Napi::Reference<Napi::ArrayBuffer>::New(buffer, 1); |
There was a problem hiding this comment.
logic: comment says "weak reference" but reference count is 1 (strong reference). should use 0 for weak reference to avoid preventing garbage collection
| _logitsBufferRef = Napi::Reference<Napi::ArrayBuffer>::New(buffer, 1); | |
| _logitsBufferRef = Napi::Reference<Napi::ArrayBuffer>::New(buffer, 0); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/SessionContext.cpp
Line: 699:699
Comment:
**logic:** comment says "weak reference" but reference count is 1 (strong reference). should use `0` for weak reference to avoid preventing garbage collection
```suggestion
_logitsBufferRef = Napi::Reference<Napi::ArrayBuffer>::New(buffer, 0);
```
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
The comment in the header says "Weak reference to detach on revocation" but that's describing the intent (we hold it so we can detach it later), not the N-API reference semantics.
Why we need refcount=1 (strong reference):
- We call buffer.Detach() later in invalidateLogits()
- If refcount=0, the buffer could be GC'd before we call Detach
- We need to keep it alive so we can explicitly invalidate it
Why refcount=0 would break things:
- Buffer gets GC'd
- _logitsBufferRef.Value() returns invalid/detached buffer
- buffer.Detach() fails or crashes
The fix is to update the misleading comment in the header, not change the refcount
feat(logits): add safety for zero copy access
No description provided.