Skip to content

Commit 7afd31c

Browse files
committed
Add readonlySavepoint function
Includes logic to temporarily disable interrupts while rolling back. Fixes #103
1 parent ad17311 commit 7afd31c

File tree

3 files changed

+86
-22
lines changed

3 files changed

+86
-22
lines changed

internal/backend/backend.go

+4-7
Original file line numberDiff line numberDiff line change
@@ -315,14 +315,11 @@ func (s *Server) getBuild(ctx context.Context, req *jsonrpc.Request) (*jsonrpc.R
315315
_, isActive := s.activeBuilds[buildID]
316316
s.activeBuildsMu.Unlock()
317317

318-
if err := sqlitex.Execute(conn, "BEGIN DEFERRED TRANSACTION;", nil); err != nil {
319-
return nil, err
318+
rollback, err := readonlySavepoint(conn)
319+
if err != nil {
320+
return nil, fmt.Errorf("get build %v: %v", buildID, err)
320321
}
321-
defer func() {
322-
if err := sqlitex.Execute(conn, "ROLLBACK TRANSACTION;", nil); err != nil {
323-
log.Errorf(ctx, "Rollback read-only transaction: %v", err)
324-
}
325-
}()
322+
defer rollback()
326323

327324
resp := &zbstorerpc.Build{
328325
ID: args.BuildID,

internal/backend/backend_store.go

+77
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"io/fs"
1414
"iter"
1515
"os"
16+
"runtime"
1617
"slices"
1718
"strings"
1819
"sync"
@@ -1033,3 +1034,79 @@ func unmarshalJSONString(data string, v any) error {
10331034
}
10341035
return nil
10351036
}
1037+
1038+
// readonlySavepoint starts a new SAVEPOINT.
1039+
// The caller is responsible for calling endFn
1040+
// to roll back the SAVEPOINT and remove it from the transaction stack.
1041+
func readonlySavepoint(conn *sqlite.Conn) (rollbackFunc func(), err error) {
1042+
name := "readonlySavepoint" // safe as names can be reused
1043+
var pc [3]uintptr
1044+
if n := runtime.Callers(0, pc[:]); n > 0 {
1045+
frames := runtime.CallersFrames(pc[:n])
1046+
if _, more := frames.Next(); more { // runtime.Callers
1047+
if _, more := frames.Next(); more { // readonlySavepoint
1048+
frame, _ := frames.Next() // caller we care about
1049+
if frame.Function != "" {
1050+
name = frame.Function
1051+
}
1052+
}
1053+
}
1054+
}
1055+
1056+
startedTransaction := conn.AutocommitEnabled()
1057+
if err := sqlitex.Execute(conn, `SAVEPOINT "`+name+`";`, nil); err != nil {
1058+
return nil, err
1059+
}
1060+
if startedTransaction {
1061+
rollbackFunc = func() {
1062+
panicError := recover()
1063+
1064+
if conn.AutocommitEnabled() {
1065+
// Transaction exited by application. Nothing to roll back.
1066+
if panicError != nil {
1067+
panic(panicError)
1068+
}
1069+
return
1070+
}
1071+
1072+
// Always run ROLLBACK even if the connection has been interrupted.
1073+
oldDoneChan := conn.SetInterrupt(nil)
1074+
defer conn.SetInterrupt(oldDoneChan)
1075+
1076+
if err := sqlitex.Execute(conn, "ROLLBACK;", nil); err != nil {
1077+
panic(err.Error())
1078+
}
1079+
if panicError != nil {
1080+
panic(panicError)
1081+
}
1082+
}
1083+
} else {
1084+
rollbackFunc = func() {
1085+
panicError := recover()
1086+
1087+
if conn.AutocommitEnabled() {
1088+
// Transaction exited by application. Nothing to roll back.
1089+
if panicError != nil {
1090+
panic(panicError)
1091+
}
1092+
return
1093+
}
1094+
1095+
// Always run ROLLBACK even if the connection has been interrupted.
1096+
oldDoneChan := conn.SetInterrupt(nil)
1097+
defer conn.SetInterrupt(oldDoneChan)
1098+
1099+
if err := sqlitex.Execute(conn, `ROLLBACK TO "`+name+`";`, nil); err != nil {
1100+
panic(err.Error())
1101+
}
1102+
if err := sqlitex.Execute(conn, `RELEASE "`+name+`";`, nil); err != nil {
1103+
panic(err.Error())
1104+
}
1105+
if panicError != nil {
1106+
panic(panicError)
1107+
}
1108+
}
1109+
}
1110+
1111+
return rollbackFunc, nil
1112+
}

internal/backend/realize.go

+5-15
Original file line numberDiff line numberDiff line change
@@ -872,7 +872,7 @@ func (b *builder) do(ctx context.Context, drvPath zbstore.Path, outputNames sets
872872
}()
873873

874874
// Save outputs as store objects.
875-
inputs, err := b.inputs(ctx, conn, drvPath)
875+
inputs, err := b.inputs(conn, drvPath)
876876
if err != nil {
877877
return err
878878
}
@@ -927,7 +927,7 @@ func (b *builder) do(ctx context.Context, drvPath zbstore.Path, outputNames sets
927927
}
928928

929929
// inputs computes the closure of all inputs used by the derivation at drvPath.
930-
func (b *builder) inputs(ctx context.Context, conn *sqlite.Conn, drvPath zbstore.Path) (map[zbstore.Path]sets.Set[equivalenceClass], error) {
930+
func (b *builder) inputs(conn *sqlite.Conn, drvPath zbstore.Path) (map[zbstore.Path]sets.Set[equivalenceClass], error) {
931931
drv := b.derivations[drvPath]
932932
if drv == nil {
933933
return nil, fmt.Errorf("input closure for %s: unknown derivation", drvPath)
@@ -952,21 +952,11 @@ func (b *builder) inputs(ctx context.Context, conn *sqlite.Conn, drvPath zbstore
952952
}
953953
}
954954

955-
startedTransaction := conn.AutocommitEnabled()
956-
if err := sqlitex.Execute(conn, "SAVEPOINT inputs;", nil); err != nil {
955+
rollback, err := readonlySavepoint(conn)
956+
if err != nil {
957957
return nil, fmt.Errorf("input closure for %s: %v", drvPath, err)
958958
}
959-
defer func() {
960-
var sql string
961-
if startedTransaction {
962-
sql = "ROLLBACK TRANSACTION;"
963-
} else {
964-
sql = "ROLLBACK TRANSACTION TO SAVEPOINT inputs;"
965-
}
966-
if err := sqlitex.Execute(conn, sql, nil); err != nil {
967-
log.Errorf(ctx, "Rollback read-only savepoint: %v", err)
968-
}
969-
}()
959+
defer rollback()
970960

971961
for _, input := range drv.InputSources.All() {
972962
err := closurePaths(conn, pathAndEquivalenceClass{path: input}, func(pe pathAndEquivalenceClass) bool {

0 commit comments

Comments
 (0)