diff --git a/src/workerd/util/sqlite-test.c++ b/src/workerd/util/sqlite-test.c++ index 0760a74b180..11c7e744779 100644 --- a/src/workerd/util/sqlite-test.c++ +++ b/src/workerd/util/sqlite-test.c++ @@ -1502,6 +1502,7 @@ class ErrorInjectableFile final: public kj::File, public kj::AtomicRefcounted { // kj::Directory that serves ErrorInjectableFiles to SQLite. class ErrorInjectableDirectory final: public kj::Directory, public kj::AtomicRefcounted { public: + kj::Maybe error; kj::Maybe> dbFile; kj::Maybe> walFile; kj::Maybe> journalFile; @@ -1530,6 +1531,9 @@ class ErrorInjectableDirectory final: public kj::Directory, public kj::AtomicRef // implements kj::Directory kj::Maybe> tryOpenFile(kj::PathPtr path) const override { + KJ_IF_SOME(e, error) { + kj::throwFatalException(e.clone()); + } return getSlot(path).map([](kj::Own& file) { return file->clone(); }); } @@ -1628,6 +1632,17 @@ KJ_TEST("SQLite open errors are tagged for DO Sentry") { expectDoSentryDisposition(exception); } +KJ_TEST("SQLite open preserves directory VFS exceptions") { + auto dir = kj::atomicRefcounted(); + dir->error = KJ_EXCEPTION(FAILED, "test-directory-vfs-error"); + SqliteDatabase::Vfs vfs(*dir); + auto exception = KJ_ASSERT_NONNULL( + kj::runCatchingExceptions([&]() { SqliteDatabase(vfs, kj::Path({"db"}), kj::none); })); + KJ_EXPECT(exception.getDescription() == "test-directory-vfs-error", exception); + auto disposition = KJ_ASSERT_NONNULL(exception.getDetail(SENTRY_DISPOSITION_DETAIL_ID)); + KJ_EXPECT(disposition.asChars() == "SENTRY_DO"_kj, exception); +} + KJ_TEST("SQLite memory metering enforces SQLITE_NOMEM when limit is exceeded") { auto dir = kj::newInMemoryDirectory(kj::nullClock()); SqliteDatabase::Vfs vfs(*dir); @@ -1695,7 +1710,9 @@ KJ_TEST("I/O exceptions pass through SQLite") { )")); // Now arrange for an error on write(). - KJ_ASSERT_NONNULL(dir->dbFile)->error = KJ_EXCEPTION(FAILED, "test-vfs-error"); + auto vfsError = KJ_EXCEPTION(FAILED, "test-vfs-error"); + vfsError.setDetail(SENTRY_DISPOSITION_DETAIL_ID, kj::heapArray("NOSENTRY"_kj.asBytes())); + KJ_ASSERT_NONNULL(dir->dbFile)->error = kj::mv(vfsError); // It should pass through. auto exception = KJ_ASSERT_NONNULL(kj::runCatchingExceptions([&]() { @@ -1704,7 +1721,8 @@ KJ_TEST("I/O exceptions pass through SQLite") { )")); })); KJ_EXPECT(exception.getDescription() == "test-vfs-error", exception); - expectDoSentryDisposition(exception); + auto disposition = KJ_ASSERT_NONNULL(exception.getDetail(SENTRY_DISPOSITION_DETAIL_ID)); + KJ_EXPECT(disposition.asChars() == "NOSENTRY"_kj, exception); } void testCriticalError(const char* expectedErrorMessage, diff --git a/src/workerd/util/sqlite.c++ b/src/workerd/util/sqlite.c++ index 0a6b7456c22..f47b7b14f86 100644 --- a/src/workerd/util/sqlite.c++ +++ b/src/workerd/util/sqlite.c++ @@ -272,7 +272,9 @@ class SqliteCallScope { // associated with an open DB connection. #define SQLITE_CALL_NODB(code, ...) \ do { \ + SqliteCallScope sqliteCallScope; \ int _ec = code; \ + if (_ec != SQLITE_OK) sqliteCallScope.rethrowVfsError(); \ if (_ec != SQLITE_OK) { \ throwDoSentryException(KJ_EXCEPTION( \ FAILED, kj::str(sqlite3_errstr(_ec), ": ", namedErrorCode(_ec)), ##__VA_ARGS__)); \ @@ -2326,7 +2328,7 @@ sqlite3_vfs SqliteDatabase::Vfs::makeKjVfs() { #define WRAP_METHOD(errorCode, block) \ auto& self KJ_UNUSED = *static_cast(vfs->pAppData); \ try block catch (kj::Exception& e) { \ - KJ_LOG(ERROR, "SQLite VFS I/O error", e); \ + reportVfsErrorCaught(kj::mv(e)); \ return errorCode; \ }