diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d563aaa..e8575929 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## v0.10.1 + +- Fixed nested transactions deadlock when authenticating with OAuth2 ([#1291](https://github.com/pocketbase/pocketbase/issues/1291)). + + ## v0.10.0 - Added `/api/health` endpoint (thanks @MarvinJWendt). diff --git a/forms/record_upsert.go b/forms/record_upsert.go index 1bd4cb6c..3e6d6e10 100644 --- a/forms/record_upsert.go +++ b/forms/record_upsert.go @@ -675,9 +675,10 @@ func (form *RecordUpsert) DrySubmit(callback func(txDao *daos.Dao) error) error return err } - // use the default app.Dao to prevent changing the transaction form.Dao + // use the default app.Dao().ConcurrentDB() to prevent changing the transaction form.Dao // and causing "transaction has already been committed or rolled back" error - return form.app.Dao().RunInTransaction(func(txDao *daos.Dao) error { + dryDao := daos.New(form.app.Dao().ConcurrentDB()) + return dryDao.RunInTransaction(func(txDao *daos.Dao) error { tx, ok := txDao.DB().(*dbx.Tx) if !ok { return errors.New("failed to get transaction db") diff --git a/forms/record_upsert_test.go b/forms/record_upsert_test.go index d38a3e32..bb8a5ac8 100644 --- a/forms/record_upsert_test.go +++ b/forms/record_upsert_test.go @@ -337,6 +337,61 @@ func TestRecordUpsertDrySubmitSuccess(t *testing.T) { } } +func TestRecordUpsertDrySubmitWithNestedTx(t *testing.T) { + app, _ := tests.NewTestApp() + defer app.Cleanup() + + collection, _ := app.Dao().FindCollectionByNameOrId("demo1") + recordBefore, err := app.Dao().FindRecordById(collection.Id, "84nmscqy84lsi1t") + if err != nil { + t.Fatal(err) + } + + formData, mp, err := tests.MockMultipartData(map[string]string{ + "title": "dry_test", + }) + if err != nil { + t.Fatal(err) + } + + txErr := app.Dao().RunInTransaction(func(txDao *daos.Dao) error { + form := forms.NewRecordUpsert(app, recordBefore) + form.SetDao(txDao) + req := httptest.NewRequest(http.MethodGet, "/", formData) + req.Header.Set(echo.HeaderContentType, mp.FormDataContentType()) + form.LoadRequest(req, "") + + callbackCalls := 0 + + result := form.DrySubmit(func(innerTxDao *daos.Dao) error { + callbackCalls++ + return nil + }) + if result != nil { + t.Fatalf("Expected nil, got error %v", result) + } + + // ensure callback was called + if callbackCalls != 1 { + t.Fatalf("Expected callbackCalls to be 1, got %d", callbackCalls) + } + + // ensure that the record changes weren't persisted + recordAfter, err := app.Dao().FindRecordById(collection.Id, recordBefore.Id) + if err != nil { + t.Fatal(err) + } + if recordAfter.GetString("title") == "dry_test" { + t.Fatalf("Expected record.title to be %v, got %v", recordAfter.GetString("title"), "dry_test") + } + + return nil + }) + if txErr != nil { + t.Fatalf("Nested transactions failure: %v", err) + } +} + func TestRecordUpsertSubmitFailure(t *testing.T) { app, _ := tests.NewTestApp() defer app.Cleanup()