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"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"hash/fnv"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -14,6 +15,20 @@ import (
|
||||||
"github.com/sipeed/picoclaw/pkg/providers"
|
"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.
|
// sessionMeta holds per-session metadata stored in a .meta.json file.
|
||||||
type sessionMeta struct {
|
type sessionMeta struct {
|
||||||
Key string `json:"key"`
|
Key string `json:"key"`
|
||||||
|
|
@ -37,7 +52,7 @@ type sessionMeta struct {
|
||||||
// append-only, which is both fast and crash-safe.
|
// append-only, which is both fast and crash-safe.
|
||||||
type JSONLStore struct {
|
type JSONLStore struct {
|
||||||
dir string
|
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.
|
// 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
|
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 {
|
func (s *JSONLStore) sessionLock(key string) *sync.Mutex {
|
||||||
v, _ := s.locks.LoadOrStore(key, &sync.Mutex{})
|
h := fnv.New32a()
|
||||||
return v.(*sync.Mutex)
|
h.Write([]byte(key))
|
||||||
|
return &s.locks[h.Sum32()%numLockShards]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *JSONLStore) jsonlPath(key string) string {
|
func (s *JSONLStore) jsonlPath(key string) string {
|
||||||
|
|
@ -126,8 +144,8 @@ func readMessages(path string, skip int) ([]providers.Message, error) {
|
||||||
|
|
||||||
var msgs []providers.Message
|
var msgs []providers.Message
|
||||||
scanner := bufio.NewScanner(f)
|
scanner := bufio.NewScanner(f)
|
||||||
// Allow up to 1 MB per line for messages with large content.
|
// Allow large lines for tool results (read_file, web search, etc.).
|
||||||
scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024)
|
scanner.Buffer(make([]byte, 0, 64*1024), maxLineSize)
|
||||||
|
|
||||||
lineNum := 0
|
lineNum := 0
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
|
|
@ -172,7 +190,7 @@ func countLines(path string) (int, error) {
|
||||||
|
|
||||||
n := 0
|
n := 0
|
||||||
scanner := bufio.NewScanner(f)
|
scanner := bufio.NewScanner(f)
|
||||||
scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024)
|
scanner.Buffer(make([]byte, 0, 64*1024), maxLineSize)
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
if len(scanner.Bytes()) > 0 {
|
if len(scanner.Bytes()) > 0 {
|
||||||
n++
|
n++
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue