Refuse a key-value write too large for its bucket before it is put (issue 774)
mesh-grants' sealed grants and the controller's merge record each met "message size exceeds maximum" (JetStream 10054) only after the put, and a writer that retries looped on a value that can never land; an update over the limit was even answered as a revision mismatch, which every compare-and-set writer retries. Quick fix: cap the two values in their modules. Thorough fix, chosen because the kind was met twice and every module writes through the SDK: the package kvlimit checks a value against its bucket's largest message (read from its stream and kept) before the put, naming the bucket, the key, the size and the limit, and turns the bus's own refusal into the same *TooLarge (errors.Is kvlimit.ErrTooLarge), so no writer retries it. The SDK's own writers use it: process.State (Put, Create, CreateTTL, Update), process.AwaitedBucket, the runtime state of a bundle (stdio KeptState, against the module state's 256 KiB) and mesh-hand-compose. A value that can outgrow its bucket is split over keys or kept in the object store with its name in the key.
This commit is contained in:
+21
-1
@@ -5,6 +5,8 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/kvlimit"
|
||||
)
|
||||
|
||||
// State — the current value of something a module keeps on the bus, which every machine sees,
|
||||
@@ -64,12 +66,30 @@ func (s KeptState) ask(verb string, params map[string]any) (json.RawMessage, err
|
||||
if err := refusedUnannounced(s.name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// A write's value is measured before it is sent: one over the bucket's limit is refused here, by name, and a
|
||||
// refusal the bus gives anyway comes back as the same *kvlimit.TooLarge, so no writer retries it (issue 774).
|
||||
size := -1
|
||||
key, _ := params["key"].(string)
|
||||
if value, ok := params["value"]; ok {
|
||||
data, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("the value for %s in the state %s: %w", key, s.name, err)
|
||||
}
|
||||
if err := kvlimit.Check("the state "+s.name, key, len(data), kvlimit.ModuleStateValueBytes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
params["value"], size = json.RawMessage(data), len(data)
|
||||
}
|
||||
c, err := live()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
params["state"] = s.name
|
||||
return c.ask("mesh/state."+verb, params, stateTimeout)
|
||||
raw, err := c.ask("mesh/state."+verb, params, stateTimeout)
|
||||
if size >= 0 {
|
||||
err = kvlimit.Refused("the state "+s.name, key, size, kvlimit.ModuleStateValueBytes, err)
|
||||
}
|
||||
return raw, err
|
||||
}
|
||||
|
||||
// Get is a key's current value, or nil when it has none.
|
||||
|
||||
Reference in New Issue
Block a user