Fix clipboard history lock recovery and merged selection
This commit is contained in:
1 parent
2c02a79655
commit
0b3ba7fbe7
2 files changed
+63
-2
No files matched your search
@@ -225,3 +225,57 @@ func TestClearRemovesTemporaryHistoryLeftByInterruptedWrite(t *testing.T) {
|
|||||||
t.Fatal("clear removed another history's file", err)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -134,6 +134,7 @@ func (p *Persistence) run(path string, watching bool) {
|
|||||||
defer tick.Stop()
|
defer tick.Stop()
|
||||||
last := HistoryUpdate{State: p.Initial, Enabled: p.InitialEnabled}
|
last := HistoryUpdate{State: p.Initial, Enabled: p.InitialEnabled}
|
||||||
var signature string
|
var signature string
|
||||||
|
var retryLock bool
|
||||||
var acknowledged uint64
|
var acknowledged uint64
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
@@ -167,7 +168,7 @@ func (p *Persistence) run(path string, watching bool) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
nextSignature := history_signature(path)
|
nextSignature := history_signature(path)
|
||||||
if nextSignature == signature {
|
if nextSignature == signature && !retryLock {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
file, err := acquire_history(path, false)
|
file, err := acquire_history(path, false)
|
||||||
@@ -182,16 +183,21 @@ func (p *Persistence) run(path string, watching bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
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 {
|
if signature != nextSignature {
|
||||||
p.report(err)
|
p.report(err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if retryLock {
|
||||||
|
p.report(nil)
|
||||||
|
}
|
||||||
if !reflect.DeepEqual(update, last) {
|
if !reflect.DeepEqual(update, last) {
|
||||||
p.publish(update)
|
p.publish(update)
|
||||||
last = update
|
last = update
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
retryLock = errors.Is(err, syscall.EWOULDBLOCK)
|
||||||
signature = nextSignature
|
signature = nextSignature
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -294,6 +300,7 @@ func apply_history(path string, job historyJob) (HistoryUpdate, error) {
|
|||||||
}
|
}
|
||||||
if !found {
|
if !found {
|
||||||
state.Entries = append([]string{text}, state.Entries...)
|
state.Entries = append([]string{text}, state.Entries...)
|
||||||
|
state.Selected++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user