Fix clipboard history lock recovery and merged selection

This commit is contained in:
ak47 committed 2026-11-07 16:49:25 -05:00
1 parent 2c02a79655
commit 0b3ba7fbe7
2 files changed
+63 -2

No files matched your search

+54
View File
@@ -225,3 +225,57 @@ func TestClearRemovesTemporaryHistoryLeftByInterruptedWrite(t *testing.T) {
t.Fatal("clear removed another history's file", err)
}
}
func TestPersistenceRetriesAfterHistoryLockTimeout(t *testing.T) {
path := filepath.Join(t.TempDir(), "private", "terminal.json")
lock, err := acquire_history(path, true)
if err != nil {
t.Fatal(err)
}
defer lock.Close()
if err := save_history(path, Snapshot{Version: 1, Entries: []string{"shared"}}); err != nil {
t.Fatal(err)
}
store := New_persistence(path, true)
defer store.Close()
if !errors.Is(store.LoadErr, syscall.EWOULDBLOCK) {
t.Fatalf("expected startup lock contention, got %v", store.LoadErr)
}
select {
case err := <-store.Errors():
if !errors.Is(err, syscall.EWOULDBLOCK) {
t.Fatal(err)
}
case <-time.After(4 * time.Second):
t.Fatal("refresh did not time out on held lock")
}
if err := lock.Close(); err != nil {
t.Fatal(err)
}
select {
case update := <-store.Updates():
if !reflect.DeepEqual(update.State.Entries, []string{"shared"}) {
t.Fatal(update)
}
case <-time.After(3 * time.Second):
t.Fatal("history did not recover after lock release")
}
if err := store.Close(); err != nil {
t.Fatalf("recovered lock failure persisted at close: %v", err)
}
}
func TestEnableMergePreservesSavedSelection(t *testing.T) {
path := filepath.Join(t.TempDir(), "private", "terminal.json")
state := Snapshot{Version: 1, Entries: []string{"newer", "selected"}, Selected: 1}
if err := save_history(path, state); err != nil {
t.Fatal(err)
}
update, err := apply_history(path, historyJob{action: "enable", state: Snapshot{Version: 1, Entries: []string{"retained"}}, limit: 10})
if err != nil {
t.Fatal(err)
}
if got := update.State.Entries[update.State.Selected]; got != "selected" {
t.Fatalf("saved selection moved to %q", got)
}
}
+9 -2
View File
@@ -134,6 +134,7 @@ func (p *Persistence) run(path string, watching bool) {
defer tick.Stop()
last := HistoryUpdate{State: p.Initial, Enabled: p.InitialEnabled}
var signature string
var retryLock bool
var acknowledged uint64
for {
select {
@@ -167,7 +168,7 @@ func (p *Persistence) run(path string, watching bool) {
continue
}
nextSignature := history_signature(path)
if nextSignature == signature {
if nextSignature == signature && !retryLock {
continue
}
file, err := acquire_history(path, false)
@@ -182,16 +183,21 @@ func (p *Persistence) run(path string, watching bool) {
}
}
if err != nil {
// Report a stable failure once, and retry after a file changes.
// Report a stable failure once. Lock contention can clear without
// changing either file, so it must also be retried on the next tick.
if signature != nextSignature {
p.report(err)
}
} else {
if retryLock {
p.report(nil)
}
if !reflect.DeepEqual(update, last) {
p.publish(update)
last = update
}
}
retryLock = errors.Is(err, syscall.EWOULDBLOCK)
signature = nextSignature
}
}
@@ -294,6 +300,7 @@ func apply_history(path string, job historyJob) (HistoryUpdate, error) {
}
if !found {
state.Entries = append([]string{text}, state.Entries...)
state.Selected++
}
}
}