mirror of
https://github.com/Buriburizaem0n/nezha_domains.git
synced 2026-09-19 17:50:12 +00:00
feat(agentcompat): add SQLite hold scoped attribution
Co-authored-by: naiba/CloudCode <hi+cloudcode@nai.ba>
This commit is contained in:
@@ -0,0 +1,239 @@
|
||||
//go:build agentcompat && linux
|
||||
|
||||
package singleton
|
||||
|
||||
import "context"
|
||||
|
||||
func (tracker *SQLiteHoldTracker) ArmSQLiteHold(journal SQLiteJournalIdentity) (SQLiteHoldSession, error) {
|
||||
return tracker.armSQLiteHold(SQLiteHoldSelectionModeKnownJournal, journal)
|
||||
}
|
||||
|
||||
func (tracker *SQLiteHoldTracker) ArmNextSQLiteHold() (SQLiteHoldSession, error) {
|
||||
return tracker.armSQLiteHold(SQLiteHoldSelectionModeNextWriter, SQLiteJournalIdentity{})
|
||||
}
|
||||
|
||||
func (tracker *SQLiteHoldTracker) armSQLiteHold(mode SQLiteHoldSelectionMode, journal SQLiteJournalIdentity) (SQLiteHoldSession, error) {
|
||||
tracker.mu.Lock()
|
||||
defer tracker.mu.Unlock()
|
||||
if tracker.session != nil {
|
||||
return SQLiteHoldSession{}, ErrSQLiteHoldSessionActive
|
||||
}
|
||||
tracker.nextSession++
|
||||
tracker.session = &sqliteHoldSessionState{
|
||||
id: SQLiteHoldSession{identity: tracker.nextSession}, mode: mode, journal: journal, notify: make(chan struct{}),
|
||||
}
|
||||
var candidates []SQLiteTransaction
|
||||
for transaction, held := range tracker.transactions {
|
||||
if tracker.isEligibleLocked(held) {
|
||||
candidates = append(candidates, transaction)
|
||||
}
|
||||
}
|
||||
if len(candidates) > 1 {
|
||||
tracker.abortSQLiteHoldWithCandidatesLocked(ErrSQLiteHoldAmbiguousCandidate, candidates)
|
||||
return SQLiteHoldSession{}, ErrSQLiteHoldAmbiguousCandidate
|
||||
}
|
||||
if len(candidates) == 1 {
|
||||
tracker.selectSQLiteHoldLocked(candidates[0])
|
||||
}
|
||||
tracker.setAttributionEnabledLocked(true)
|
||||
return tracker.session.id, nil
|
||||
}
|
||||
|
||||
func (tracker *SQLiteHoldTracker) WaitSQLiteHold(ctx context.Context, session SQLiteHoldSession, target SQLiteHoldWaitTarget) (SQLiteHoldSnapshot, error) {
|
||||
if !target.valid() {
|
||||
return SQLiteHoldSnapshot{}, ErrSQLiteHoldInvalidWaitTarget
|
||||
}
|
||||
for {
|
||||
tracker.mu.Lock()
|
||||
if tracker.session == nil || tracker.session.id != session {
|
||||
if tracker.terminal != nil && tracker.terminal.id == session {
|
||||
if tracker.terminal.released {
|
||||
snapshot := SQLiteHoldSnapshot{SessionID: session.ID(), Selected: tracker.terminal.hasSelected, Transaction: tracker.terminal.selected, Finalizing: true, Released: true}
|
||||
tracker.mu.Unlock()
|
||||
return snapshot, nil
|
||||
}
|
||||
cause := tracker.terminal.cause
|
||||
tracker.mu.Unlock()
|
||||
return SQLiteHoldSnapshot{}, cause
|
||||
}
|
||||
tracker.mu.Unlock()
|
||||
return SQLiteHoldSnapshot{}, ErrSQLiteHoldStaleSession
|
||||
}
|
||||
if tracker.waitTargetReachedLocked(target) {
|
||||
snapshot, err := tracker.snapshotLocked(session)
|
||||
tracker.mu.Unlock()
|
||||
return snapshot, err
|
||||
}
|
||||
notify := tracker.session.notify
|
||||
tracker.mu.Unlock()
|
||||
select {
|
||||
case <-notify:
|
||||
case <-ctx.Done():
|
||||
return SQLiteHoldSnapshot{}, ctx.Err()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (tracker *SQLiteHoldTracker) BeginSQLiteFinalization(transaction SQLiteTransaction) (*SQLiteHoldFinalization, error) {
|
||||
tracker.mu.Lock()
|
||||
defer tracker.mu.Unlock()
|
||||
if tracker.session == nil || !tracker.session.hasSelected || tracker.session.selected != transaction {
|
||||
return nil, ErrSQLiteHoldNotSelected
|
||||
}
|
||||
return tracker.beginSQLiteFinalizationLocked(transaction)
|
||||
}
|
||||
|
||||
func (tracker *SQLiteHoldTracker) BeginSQLiteCommitFinalization(transaction SQLiteTransaction) (*SQLiteHoldFinalization, error) {
|
||||
tracker.mu.Lock()
|
||||
defer tracker.mu.Unlock()
|
||||
if tracker.session != nil && tracker.session.hasSelected && tracker.session.selected == transaction {
|
||||
return tracker.beginSQLiteFinalizationLocked(transaction)
|
||||
}
|
||||
if cause, ok := tracker.causes[transaction]; ok {
|
||||
return nil, cause
|
||||
}
|
||||
return nil, ErrSQLiteHoldNotSelected
|
||||
}
|
||||
|
||||
func (tracker *SQLiteHoldTracker) ReleaseSQLiteHold(session SQLiteHoldSession) error {
|
||||
tracker.mu.Lock()
|
||||
defer tracker.mu.Unlock()
|
||||
if !tracker.matchesSessionLocked(session) || tracker.session.released {
|
||||
return ErrSQLiteHoldStaleSession
|
||||
}
|
||||
if !tracker.session.hasSelected || tracker.session.finalization == nil {
|
||||
return ErrSQLiteHoldFinalizationNotStarted
|
||||
}
|
||||
tracker.session.released = true
|
||||
tracker.setAttributionEnabledLocked(false)
|
||||
close(tracker.session.finalization.done)
|
||||
tracker.notifySQLiteHoldLocked()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tracker *SQLiteHoldTracker) AbortSQLiteHold(session SQLiteHoldSession) error {
|
||||
tracker.mu.Lock()
|
||||
defer tracker.mu.Unlock()
|
||||
if !tracker.matchesSessionLocked(session) || tracker.session.released {
|
||||
return ErrSQLiteHoldStaleSession
|
||||
}
|
||||
tracker.abortSQLiteHoldLocked(ErrSQLiteHoldAborted)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tracker *SQLiteHoldTracker) SQLiteHoldSnapshot(session SQLiteHoldSession) (SQLiteHoldSnapshot, error) {
|
||||
tracker.mu.Lock()
|
||||
defer tracker.mu.Unlock()
|
||||
if !tracker.matchesSessionLocked(session) {
|
||||
return SQLiteHoldSnapshot{}, ErrSQLiteHoldStaleSession
|
||||
}
|
||||
return tracker.snapshotLocked(session)
|
||||
}
|
||||
|
||||
func (tracker *SQLiteHoldTracker) snapshotLocked(session SQLiteHoldSession) (SQLiteHoldSnapshot, error) {
|
||||
snapshot := SQLiteHoldSnapshot{SessionID: session.ID(), Mode: tracker.session.mode, Selected: tracker.session.hasSelected, Journal: tracker.session.journal, Released: tracker.session.released}
|
||||
if !tracker.session.hasSelected {
|
||||
return snapshot, nil
|
||||
}
|
||||
held := tracker.transactions[tracker.session.selected]
|
||||
if held == nil {
|
||||
return SQLiteHoldSnapshot{}, ErrSQLiteHoldNotSelected
|
||||
}
|
||||
snapshot.Transaction = held.transaction
|
||||
if held.hasWrite {
|
||||
snapshot.Operation, snapshot.Table, snapshot.Journal = held.write.Update.Operation, held.write.Update.Table, held.write.Update.Journal
|
||||
snapshot.StackHash, snapshot.FirstNezhaFrame = held.write.Origin.StackHash, held.write.Origin.FirstNezhaFrame
|
||||
} else {
|
||||
snapshot.Operation, snapshot.Table, snapshot.Journal = held.update.Operation, held.update.Table, held.update.Journal
|
||||
snapshot.StackHash, snapshot.FirstNezhaFrame = held.origin.StackHash, held.origin.FirstNezhaFrame
|
||||
}
|
||||
snapshot.Finalizing = held.finalizing
|
||||
return snapshot, nil
|
||||
}
|
||||
|
||||
func (tracker *SQLiteHoldTracker) selectSQLiteHoldLocked(transaction SQLiteTransaction) error {
|
||||
if !tracker.session.hasSelected {
|
||||
held := tracker.transactions[transaction]
|
||||
tracker.session.selected, tracker.session.hasSelected = transaction, true
|
||||
if tracker.session.mode == SQLiteHoldSelectionModeNextWriter {
|
||||
tracker.session.journal = held.update.Journal
|
||||
}
|
||||
tracker.notifySQLiteHoldLocked()
|
||||
return nil
|
||||
}
|
||||
if tracker.session.selected == transaction {
|
||||
return nil
|
||||
}
|
||||
tracker.abortSQLiteHoldWithCandidatesLocked(ErrSQLiteHoldAmbiguousCandidate, []SQLiteTransaction{tracker.session.selected, transaction})
|
||||
return ErrSQLiteHoldAmbiguousCandidate
|
||||
}
|
||||
|
||||
func (tracker *SQLiteHoldTracker) matchesSessionLocked(session SQLiteHoldSession) bool {
|
||||
return tracker.session != nil && tracker.session.id == session
|
||||
}
|
||||
|
||||
func (tracker *SQLiteHoldTracker) activeSessionHasSelectedTransactionLocked(transaction SQLiteTransaction) bool {
|
||||
return tracker.session != nil && tracker.session.hasSelected && tracker.session.selected == transaction
|
||||
}
|
||||
|
||||
func (tracker *SQLiteHoldTracker) beginSQLiteFinalizationLocked(transaction SQLiteTransaction) (*SQLiteHoldFinalization, error) {
|
||||
held, ok := tracker.transactionLocked(transaction)
|
||||
if !ok || held.finalizing {
|
||||
return nil, ErrSQLiteHoldFinalizationStarted
|
||||
}
|
||||
held.finalizing = true
|
||||
finalization := &SQLiteHoldFinalization{done: make(chan struct{})}
|
||||
tracker.session.finalization = finalization
|
||||
tracker.notifySQLiteHoldLocked()
|
||||
return finalization, nil
|
||||
}
|
||||
|
||||
func (tracker *SQLiteHoldTracker) waitTargetReachedLocked(target SQLiteHoldWaitTarget) bool {
|
||||
switch target {
|
||||
case SQLiteHoldWaitSelected:
|
||||
return tracker.session.hasSelected
|
||||
case SQLiteHoldWaitFinalizing:
|
||||
return tracker.session.finalization != nil
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (target SQLiteHoldWaitTarget) valid() bool {
|
||||
return target == SQLiteHoldWaitSelected || target == SQLiteHoldWaitFinalizing
|
||||
}
|
||||
|
||||
// Closing and replacing the channel under mu makes a snapshot-to-wait handoff race-free.
|
||||
func (tracker *SQLiteHoldTracker) notifySQLiteHoldLocked() {
|
||||
close(tracker.session.notify)
|
||||
tracker.session.notify = make(chan struct{})
|
||||
}
|
||||
|
||||
func (tracker *SQLiteHoldTracker) abortSQLiteHoldLocked(cause error) {
|
||||
tracker.abortSQLiteHoldWithCandidatesLocked(cause, nil)
|
||||
}
|
||||
|
||||
func (tracker *SQLiteHoldTracker) abortSQLiteHoldWithCandidatesLocked(cause error, candidates []SQLiteTransaction) {
|
||||
tracker.setAttributionEnabledLocked(false)
|
||||
if tracker.session.finalization != nil {
|
||||
tracker.session.finalization.err = cause
|
||||
close(tracker.session.finalization.done)
|
||||
}
|
||||
if tracker.session.hasSelected {
|
||||
tracker.causes[tracker.session.selected] = cause
|
||||
}
|
||||
for _, transaction := range candidates {
|
||||
tracker.causes[transaction] = cause
|
||||
}
|
||||
tracker.terminal = &sqliteHoldTerminalState{id: tracker.session.id, selected: tracker.session.selected, hasSelected: tracker.session.hasSelected, cause: cause}
|
||||
tracker.notifySQLiteHoldLocked()
|
||||
tracker.session = nil
|
||||
}
|
||||
|
||||
func (tracker *SQLiteHoldTracker) setAttributionEnabledLocked(enabled bool) {
|
||||
if tracker.attributionEnabled == nil {
|
||||
return
|
||||
}
|
||||
// Attribution is hold-scoped because ordinary GORM RETURNING closes rows before EOF.
|
||||
tracker.attributionEnabled.Store(enabled)
|
||||
}
|
||||
Reference in New Issue
Block a user