fix(memory): bound lock memory and increase scanner buffer
Address feedback from @yinwm for long-running daemon use: - Replace sync.Map with a fixed-size sharded lock array (64 mutexes). Keys are mapped via FNV hash, so memory is O(1) regardless of how many sessions are created over the process lifetime. - Increase scanner buffer cap from 1 MB to 10 MB. Tool results (read_file on large files, web search responses) can easily exceed 1 MB. The scanner still starts at 64 KB and only grows as needed.
This commit is contained in:
parent
5d73ee2d9a
commit
d55e5540af
1 changed files with 25 additions and 7 deletions
|
|
@ -5,6 +5,7 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
|
@ -14,6 +15,20 @@ import (
|
|||
"github.com/sipeed/picoclaw/pkg/providers"
|
||||
)
|
||||
|
||||
const (
|
||||
// numLockShards is the fixed number of mutexes used to serialize
|
||||
// per-session access. Using a sharded array instead of a map keeps
|
||||
// memory bounded regardless of how many sessions are created over
|
||||
// the lifetime of the process — important for a long-running daemon.
|
||||
numLockShards = 64
|
||||
|
||||
// maxLineSize is the maximum size of a single JSON line in a .jsonl
|
||||
// file. Tool results (read_file, web search, etc.) can be large, so
|
||||
// we set a generous limit. The scanner starts at 64 KB and grows
|
||||
// only as needed up to this cap.
|
||||
maxLineSize = 10 * 1024 * 1024 // 10 MB
|
||||
)
|
||||
|
||||
// sessionMeta holds per-session metadata stored in a .meta.json file.
|
||||
type sessionMeta struct {
|
||||
Key string `json:"key"`
|
||||
|
|
@ -37,7 +52,7 @@ type sessionMeta struct {
|
|||
// append-only, which is both fast and crash-safe.
|
||||
type JSONLStore struct {
|
||||
dir string
|
||||
locks sync.Map // map[string]*sync.Mutex, one per session
|
||||
locks [numLockShards]sync.Mutex
|
||||
}
|
||||
|
||||
// NewJSONLStore creates a new JSONL-backed store rooted at dir.
|
||||
|
|
@ -49,10 +64,13 @@ func NewJSONLStore(dir string) (*JSONLStore, error) {
|
|||
return &JSONLStore{dir: dir}, nil
|
||||
}
|
||||
|
||||
// sessionLock returns (or creates) a per-session mutex.
|
||||
// sessionLock returns a mutex for the given session key.
|
||||
// Keys are mapped to a fixed pool of shards via FNV hash, so
|
||||
// memory usage is O(1) regardless of total session count.
|
||||
func (s *JSONLStore) sessionLock(key string) *sync.Mutex {
|
||||
v, _ := s.locks.LoadOrStore(key, &sync.Mutex{})
|
||||
return v.(*sync.Mutex)
|
||||
h := fnv.New32a()
|
||||
h.Write([]byte(key))
|
||||
return &s.locks[h.Sum32()%numLockShards]
|
||||
}
|
||||
|
||||
func (s *JSONLStore) jsonlPath(key string) string {
|
||||
|
|
@ -126,8 +144,8 @@ func readMessages(path string, skip int) ([]providers.Message, error) {
|
|||
|
||||
var msgs []providers.Message
|
||||
scanner := bufio.NewScanner(f)
|
||||
// Allow up to 1 MB per line for messages with large content.
|
||||
scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024)
|
||||
// Allow large lines for tool results (read_file, web search, etc.).
|
||||
scanner.Buffer(make([]byte, 0, 64*1024), maxLineSize)
|
||||
|
||||
lineNum := 0
|
||||
for scanner.Scan() {
|
||||
|
|
@ -172,7 +190,7 @@ func countLines(path string) (int, error) {
|
|||
|
||||
n := 0
|
||||
scanner := bufio.NewScanner(f)
|
||||
scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024)
|
||||
scanner.Buffer(make([]byte, 0, 64*1024), maxLineSize)
|
||||
for scanner.Scan() {
|
||||
if len(scanner.Bytes()) > 0 {
|
||||
n++
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue