wavetermdev/waveterm · warning
flush already in progress
Error message
flush already in progress
What it means
FlushCache is single-flight per FileStore: an atomic setUnlessFlushing guard ensures only one flush runs at a time. Calling FlushCache while a previous flush (e.g. started by the background runFlushWithNewContext loop) is still in progress returns this error immediately instead of queueing a second flush.
Source
Thrown at pkg/filestore/blockstore.go:399
// returns (offset, data, error)
func (s *FileStore) ReadFile(ctx context.Context, zoneId string, name string) (rtnOffset int64, rtnData []byte, rtnErr error) {
withLock(s, zoneId, name, func(entry *CacheEntry) error {
rtnOffset, rtnData, rtnErr = entry.readAt(ctx, 0, 0, true)
return nil
})
return
}
type FlushStats struct {
FlushDuration time.Duration
NumDirtyEntries int
NumCommitted int
}
func (s *FileStore) FlushCache(ctx context.Context) (stats FlushStats, rtnErr error) {
wasFlushing := s.setUnlessFlushing()
if wasFlushing {
return stats, fmt.Errorf("flush already in progress")
}
defer s.setIsFlushing(false)
startTime := time.Now()
defer func() {
stats.FlushDuration = time.Since(startTime)
}()
// get a copy of dirty keys so we can iterate without the lock
dirtyCacheKeys := s.getDirtyCacheKeys()
stats.NumDirtyEntries = len(dirtyCacheKeys)
for _, key := range dirtyCacheKeys {
err := withLock(s, key.ZoneId, key.Name, func(entry *CacheEntry) error {
return entry.flushToDB(ctx, false)
})
if ctx.Err() != nil {
// transient error (also must stop the loop)
return stats, ctx.Err()
}View on GitHub (pinned to a4447c1563)
Solutions
- Treat this error as benign: skip or retry after a short delay, since a flush is already running.
- Coordinate with the internal flush loop — don't call FlushCache manually if runFlushWithNewContext is enabled.
- Serialize flush triggers through a single goroutine/channel in your app.
Example fix
// before
if err := fs.FlushCache(ctx); err != nil { return err }
// after
if _, err := fs.FlushCache(ctx); err != nil && !strings.Contains(err.Error(), "flush already in progress") {
return err
} Defensive patterns
Strategy: try-catch
Try / catch
stats, err := fs.FlushCache(ctx)
if err != nil && strings.Contains(err.Error(), "flush already in progress") {
return nil // benign: a flush is already running
} else if err != nil {
return err
} Prevention
- Don't call FlushCache manually when the internal flush loop is active.
- Route all manual flush requests through one goroutine/channel.
- Treat this error as informational, not fatal.
When it happens
Trigger: Calling FlushCache from application code while the internal flush loop (runFlushWithNewContext) or another goroutine is already flushing; calling FlushCache concurrently from multiple request handlers.
Common situations: Shutdown hooks calling FlushCache at the same time as the periodic flusher; load tests hammering FlushCache concurrently; a long flush (many dirty entries) overlapping a manual flush.
Related errors
- error flushing cache entry[%v]: %v
- no webcontents found with blockid ${data.blockid}
- error appending to blockfile: %w
- error truncating blockfile: %w
- error creating block term file: %w
AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01).
Data as JSON: /api/errors/6999ebd0b9ce98d5.
Report an issue: GitHub.