diff --git a/build/BUILD.sqlite3 b/build/BUILD.sqlite3 index b107aeec313..c280d524de6 100644 --- a/build/BUILD.sqlite3 +++ b/build/BUILD.sqlite3 @@ -15,6 +15,7 @@ SQLITE_DEFINES = [ "SQLITE_ENABLE_MATH_FUNCTIONS", "SQLITE_DEFAULT_FOREIGN_KEYS=1", "SQLITE_ENABLE_UPDATE_DELETE_LIMIT", + "SQLITE_OMIT_SHARED_CACHE", ] SQLITE_DEFINES_FOR_LEMON = " ".join(["-D" + x for x in SQLITE_DEFINES]) @@ -60,7 +61,9 @@ genrule( # lemon requires lempar.c to be in the current working # directory, and parse.y has to be in a writable directory # since the output files are created adjacent to it. - "cp $(SRCS) . " + + # Use -f to overwrite if lempar.c already exists in the working + # directory. + "cp -f $(SRCS) . " + # Creates parse.c and parse.h "&& $(execpath :lemon) {} parse.y ".format(SQLITE_DEFINES_FOR_LEMON) + @@ -93,7 +96,7 @@ genrule( ], cmd = ( # Same as :parse_ch - "cp $(SRCS) . " + + "cp -f $(SRCS) . " + "&& $(execpath :lemon) {} fts5parse.y ".format(SQLITE_DEFINES_FOR_LEMON) + # Bazel expects genrule outputs to be in RULEDIR "&& cp fts5parse.h fts5parse.c $(RULEDIR)" diff --git a/src/workerd/io/limit-enforcer.h b/src/workerd/io/limit-enforcer.h index 36f2060952f..707a8a3e3f4 100644 --- a/src/workerd/io/limit-enforcer.h +++ b/src/workerd/io/limit-enforcer.h @@ -129,6 +129,7 @@ class LimitEnforcer { // not be called while in a JS scope, i.e. when `enterJs()` has been called and the returned // object not yet dropped. virtual void topUpActor() = 0; + // TODO(cleanup): This is called in WebSocket and JsRpcTargetBase when receiving an event, but // should we do something more generic like use a membrane to detect any incoming RPC call? @@ -175,6 +176,7 @@ class LimitEnforcer { // Returns a promise that will reject if and when a limit is exceeded that prevents further // JavaScript execution, such as the CPU or memory limit. virtual kj::Promise onLimitsExceeded() = 0; + // Sets a callback to call when the cpu limit is nearly exceeded. The callback must be signal safe // and cannot take the isolate lock. virtual void setCpuLimitNearlyExceededCallback(kj::Function) = 0; @@ -188,6 +190,10 @@ class LimitEnforcer { // Only used downstream for internal metrics. virtual kj::Duration consumeTimeElapsedForPeriodicLogging() = 0; + + // Returns the last snapshotted SQLite memory usage for the current actor, in bytes. Returns 0 + // for non-actor isolates. + virtual size_t getSqliteMemoryUsage() const = 0; }; } // namespace workerd diff --git a/src/workerd/server/server.c++ b/src/workerd/server/server.c++ index 3108eb56c52..cbe50e6747e 100644 --- a/src/workerd/server/server.c++ +++ b/src/workerd/server/server.c++ @@ -3539,6 +3539,9 @@ class Server::WorkerService final: public Service, kj::Duration consumeTimeElapsedForPeriodicLogging() override { return 0 * kj::SECONDS; } + size_t getSqliteMemoryUsage() const override { + return 0; + } }; struct FutureSubrequestChannel { diff --git a/src/workerd/tests/test-fixture.c++ b/src/workerd/tests/test-fixture.c++ index 9b2586ebb27..0455234b956 100644 --- a/src/workerd/tests/test-fixture.c++ +++ b/src/workerd/tests/test-fixture.c++ @@ -207,6 +207,9 @@ struct MockLimitEnforcer final: public LimitEnforcer { kj::Duration consumeTimeElapsedForPeriodicLogging() override { return 0 * kj::SECONDS; } + size_t getSqliteMemoryUsage() const override { + return 0; + } }; struct MockIsolateLimitEnforcer final: public IsolateLimitEnforcer { diff --git a/src/workerd/util/sqlite.c++ b/src/workerd/util/sqlite.c++ index d76e2332e6b..363612ba910 100644 --- a/src/workerd/util/sqlite.c++ +++ b/src/workerd/util/sqlite.c++ @@ -992,6 +992,13 @@ void SqliteDatabase::reset() { vfs.directory.remove(path); } + // Reset the SQLite memory counter to zero after closing the connection. sqlite3_close() frees + // all internal SQLite allocations, but those frees may have been partially unaccounted (e.g. + // if some allocations were made outside of any SqliteMemoryScope). Zeroing here ensures the + // counter correctly reflects the empty state before init() charges the fresh connection's + // allocations, preventing spurious SQLITE_NOMEM errors during re-initialization. + sqliteMemoryBytes = 0; + KJ_ON_SCOPE_FAILURE(maybeDb = kj::none); init(kj::WriteMode::CREATE | kj::WriteMode::MODIFY); diff --git a/src/workerd/util/sqlite.h b/src/workerd/util/sqlite.h index bb2b5a4a4d4..c9ae767c742 100644 --- a/src/workerd/util/sqlite.h +++ b/src/workerd/util/sqlite.h @@ -91,6 +91,17 @@ class SqliteDatabase { kj::Maybe maybeMode = kj::none, SqliteObserver& sqliteObserver = SqliteObserver::DEFAULT, kj::Maybe actorAccountLimits = kj::none); + + // Returns a pointer to the per-actor SQLite memory byte counter. + size_t* getSqliteMemoryCounter() { + return &sqliteMemoryBytes; + } + + // Returns the current value of the per-actor SQLite memory byte counter for metrics reporting. + size_t getSqliteMemoryBytes() const { + return sqliteMemoryBytes; + } + ~SqliteDatabase() noexcept(false); KJ_DISALLOW_COPY_AND_MOVE(SqliteDatabase); @@ -317,6 +328,9 @@ class SqliteDatabase { SqliteObserver& sqliteObserver; kj::Maybe actorAccountLimits; + // The amound of memory in bytes used by this actor in bytes for use by sqlite3_mem_methods. + size_t sqliteMemoryBytes = 0; + // This pointer can be left null if a call to reset() failed to re-open the database. kj::Maybe maybeDb;