subagentNameDraws = 1000
)
-// newSubagents returns the subagents in dir, one directory for each name. The
-// clock gives the dates of their messages, and draw gives the names of new
-// subagents.
-func newSubagents(dir string, clock func() time.Time, draw func() string) *Subagents {
- return &Subagents{
- dir: dir,
- clock: clock,
- draw: draw,
- open: make(map[string]*Subagent),
- }
-}
-
// Subagents holds the subagents of one agent. Each subagent has a directory
// named for it, which holds its log. A log takes an exclusive lock, so
// Subagents opens the log of a name once and keeps it open until Close. Its
clock func() time.Time
draw func() string
- mutex sync.Mutex
- open map[string]*Subagent
+ mutex sync.Mutex
+ open map[string]*Subagent
+ reserved map[string]bool
+}
+
+// Subagent is one subagent, its directory, and its log. Its mutex protects the
+// log, because the run of the subagent writes it while the agent reads it with
+// zoom.
+type Subagent struct {
+ name string
+ dir string
+ mutex sync.Mutex
+ log *Log
+ syncer *Syncer
+}
+
+// newSubagents returns the subagents in dir, one directory for each name. The
+// clock gives the dates of their messages, and draw gives the names of new
+// subagents.
+func newSubagents(dir string, clock func() time.Time, draw func() string) *Subagents {
+ return &Subagents{
+ dir: dir,
+ clock: clock,
+ draw: draw,
+ open: make(map[string]*Subagent),
+ reserved: make(map[string]bool),
+ }
}
// Create makes the directory and the log of a new subagent. It draws a name
for range subagentNameDraws {
name := s.draw()
+ s.mutex.Lock()
+
+ reserved := s.reserved[name]
+ s.mutex.Unlock()
+
+ if reserved {
+ continue
+ }
+
err = os.Mkdir(filepath.Join(s.dir, name), 0o700)
if errors.Is(err, fs.ErrExist) {
continue
return nil, fmt.Errorf("no free subagent name after %d draws", subagentNameDraws)
}
+// Reserve keeps a child name out of future subagent draws. An existing subagent
+// directory or a reserved name is refused.
+func (s *Subagents) Reserve(name string) error {
+ s.mutex.Lock()
+ defer s.mutex.Unlock()
+
+ if s.reserved[name] {
+ return fmt.Errorf("name %q is already used", name)
+ }
+
+ _, err := os.Lstat(filepath.Join(s.dir, name))
+ if err == nil {
+ return fmt.Errorf("name %q is already used by a subagent", name)
+ }
+
+ if !errors.Is(err, os.ErrNotExist) {
+ return fmt.Errorf("check subagent name: %w", err)
+ }
+
+ s.reserved[name] = true
+
+ return nil
+}
+
+// Release frees a reservation after a child fails to publish. It does not
+// remove a subagent directory or change an open log.
+func (s *Subagents) Release(name string) {
+ s.mutex.Lock()
+ defer s.mutex.Unlock()
+
+ delete(s.reserved, name)
+}
+
// Get returns the subagent name with its log open. A name that is not valid or
// that has no directory is an unknown agent.
func (s *Subagents) Get(ctx context.Context, name string) (*Subagent, error) {
log: log,
syncer: syncer,
}
+
s.open[name] = subagent
return subagent, nil
return err
}
-// Subagent is one subagent, its directory, and its log. Its mutex protects the
-// log, because the run of the subagent writes it while the agent reads it with
-// zoom.
-type Subagent struct {
- name string
- dir string
- mutex sync.Mutex
- log *Log
- syncer *Syncer
-}
-
// Append logs a message of the subagent.
func (a *Subagent) Append(kind, text string) (Message, error) {
a.mutex.Lock()