fix(core): prevent clipboard & wellbeing data loss from concurrent sessions

Port 2.0
This commit is contained in:
purian23
2026-10-23 01:42:07 -04:00
parent 5a20a8f162
commit c7692f830c
4 changed files with 55 additions and 6 deletions
+11 -6
View File
@@ -29,6 +29,7 @@ import (
_ "golang.org/x/image/webp"
bolt "go.etcd.io/bbolt"
berrors "go.etcd.io/bbolt/errors"
wlclient "github.com/duxlabs-dev/dux-go/wayland/client"
"github.com/duxlabs-dev/dux-go/wayland/ext_data_control"
@@ -55,8 +56,14 @@ func NewManager(wlCtx wlcontext.WaylandContext, config Config) (*Manager, error)
configPath, _ := getConfigPath()
db, err := openDB(dbPath)
if err != nil {
return nil, fmt.Errorf("failed to open db: %w", err)
}
m := &Manager{
config: config,
db: db,
configPath: configPath,
display: display,
wlCtx: wlCtx,
@@ -69,6 +76,7 @@ func NewManager(wlCtx wlcontext.WaylandContext, config Config) (*Manager, error)
if !config.Disabled {
if err := m.setupRegistry(); err != nil {
db.Close()
return nil, err
}
}
@@ -78,12 +86,6 @@ func NewManager(wlCtx wlcontext.WaylandContext, config Config) (*Manager, error)
go m.watchConfig()
db, err := openDB(dbPath)
if err != nil {
return nil, fmt.Errorf("failed to open db: %w", err)
}
m.db = db
if err := m.migrateHashes(); err != nil {
log.Errorf("Failed to migrate hashes: %v", err)
}
@@ -117,6 +119,9 @@ func openDB(path string) (*bolt.DB, error) {
if err == nil {
return db, nil
}
if errors.Is(err, berrors.ErrTimeout) {
return nil, err
}
log.Errorf("Clipboard db corrupted, salvaging readable entries: %v", err)
@@ -887,6 +887,24 @@ func TestOpenDB_HealsGarbageFile(t *testing.T) {
require.NoError(t, err)
}
func TestOpenDB_LockedByAnotherProcessKeepsHistory(t *testing.T) {
path := filepath.Join(t.TempDir(), "db")
holder, err := openDB(path)
require.NoError(t, err)
m := &Manager{config: DefaultConfig(), db: holder, dbPath: path}
require.NoError(t, m.storeEntry(Entry{Data: []byte("x"), MimeType: "text/plain"}))
_, err = openDB(path)
require.Error(t, err)
require.NoError(t, holder.Close())
reopened, err := openDB(path)
require.NoError(t, err)
defer reopened.Close()
m = &Manager{config: DefaultConfig(), db: reopened, dbPath: path}
assert.Len(t, m.GetHistory(), 1)
}
func TestStoreEntry_CorruptDBReturnsErrorNotPanic(t *testing.T) {
for _, tt := range corruptDBCases {
t.Run(tt.name, func(t *testing.T) {
@@ -194,3 +194,24 @@ func TestAlertRecordSurvivesRestart(t *testing.T) {
assert.Nil(t, s.Limit, "the alert already fired before the restart")
}
}
func TestOpenDBLockedByAnotherProcessKeepsData(t *testing.T) {
path := filepath.Join(t.TempDir(), "db")
c := &clock{now: time.Date(2026, 10, 1, 12, 0, 0, 0, time.UTC)}
now := func() time.Time { return c.now }
m, err := newManager(path, now)
require.NoError(t, err)
m.SetState("firefox", true, 1)
c.advance(10 * time.Second)
m.Flush()
_, err = newManager(path, now)
require.Error(t, err)
m.Close()
m, err = newManager(path, now)
require.NoError(t, err)
defer m.Close()
assert.Equal(t, int64(10), m.Summary(1)[0].Active)
}
+5
View File
@@ -2,6 +2,7 @@ package wellbeing
import (
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
@@ -11,6 +12,7 @@ import (
"github.com/duxlabs-dev/dux-shell/core/internal/log"
"github.com/duxlabs-dev/dux-shell/core/internal/utils"
bolt "go.etcd.io/bbolt"
berrors "go.etcd.io/bbolt/errors"
)
var (
@@ -31,6 +33,9 @@ func openDB(path string) (*bolt.DB, error) {
if err == nil {
return db, nil
}
if errors.Is(err, berrors.ErrTimeout) {
return nil, err
}
log.Errorf("Wellbeing db unreadable, starting over: %v", err)
corruptPath := path + ".corrupt"
os.Remove(corruptPath)