X-Git-Url: https://git.giacomolli.uk/onechat/blobdiff_plain/baf7a940be8927278e3b4b87e7366cebfc16532f..d302957c09b192d0fbc09287ba7dcb7c32426556:/memory.go?ds=inline diff --git a/memory.go b/memory.go index 9846c98..5ea12b7 100644 --- a/memory.go +++ b/memory.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "log" "os" "path/filepath" "strings" @@ -12,13 +13,19 @@ import ( "time" ) -// errMemoryNoView tells that a chat has messages but no saved view. -var errMemoryNoView = errors.New("the chat has messages but no saved view") +var ( + // errMemoryNoView tells that a chat has messages but no saved view. + errMemoryNoView = errors.New("the chat has messages but no saved view") + + // errMemoryUnsettled tells that a line of the view stays pending, + // because its build failed. + errMemoryUnsettled = errors.New("a summary of the view failed, and it is tried again at the next message") +) // openMemory opens the chat in root and starts the builds that are ready. The -// clock gives the dates of new messages and the days of new nodes. The builds -// stop when ctx ends or at Close. -func openMemory(ctx context.Context, root string, clock func() time.Time, summarizer Summarizer) (*Memory, error) { +// clock gives the dates of new messages and the days of new nodes. logger gets +// one line for each failed build. The builds stop when ctx ends or at Close. +func openMemory(ctx context.Context, root string, clock func() time.Time, summarizer Summarizer, logger *log.Logger) (*Memory, error) { log, err := openLog(ctx, root, clock) if err != nil { return nil, err @@ -29,6 +36,7 @@ func openMemory(ctx context.Context, root string, clock func() time.Time, summar log: log, queue: newBuildQueue(), summarizer: summarizer, + logger: logger, changed: make(chan struct{}), } @@ -100,6 +108,7 @@ type Memory struct { cview *View queue *BuildQueue summarizer Summarizer + logger *log.Logger ctx context.Context cancel context.CancelFunc workers sync.WaitGroup @@ -195,6 +204,34 @@ func (m *Memory) Wait(ctx context.Context) error { } } +// Settle returns when the chat view has no pending line, so that every message +// so far is summarized. A pending line with no build that runs belongs to a +// failed build, which waits for the next message, so Settle returns +// errMemoryUnsettled in place of a wait that never ends. +func (m *Memory) Settle(ctx context.Context) error { + for { + m.mutex.Lock() + pending := m.chat.Pending() + running := m.queue.Running() + changed := m.changed + m.mutex.Unlock() + + if !pending { + return nil + } + + if running == 0 { + return errMemoryUnsettled + } + + select { + case <-changed: + case <-ctx.Done(): + return ctx.Err() + } + } +} + // Flush returns when every write before the call is durable. func (m *Memory) Flush(ctx context.Context) error { return m.syncer.Flush(ctx) @@ -360,7 +397,9 @@ func (m *Memory) dispatch() { task, err := m.task(key) if err != nil { + m.failed(key, fmt.Errorf("read its input: %w", err)) m.queue.Fail(key) + continue } @@ -527,6 +566,10 @@ func (m *Memory) run(key NodeKey, task Task) { m.syncer.Written() } + if err != nil && m.ctx.Err() == nil { + m.failed(key, err) + } + if err != nil { m.queue.Fail(key) } else { @@ -538,6 +581,11 @@ func (m *Memory) run(key NodeKey, task Task) { m.dispatch() } +// failed logs the failed build of the node key with its cause err. +func (m *Memory) failed(key NodeKey, err error) { + m.logger.Printf("summary failed, retry at next message: node (%d, %d): %v", key.level, key.index, err) +} + // ready tells if both children of the node key are built and the node is not. func (m *Memory) ready(key NodeKey) (bool, error) { for _, index := range []int{2 * key.index, 2*key.index + 1} {