Skip to content
File

Blob: src/workerd/util/sqlite-metering.h

cpp70 lines
1// Copyright (c) 2026 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4 
5#pragma once
6 
7#include <stddef.h>
8 
9#include <kj/common.h>
10 
11namespace workerd {
12 
13// This module implements per-database SQLite memory metering.
14//
15// SQLite uses a single process-wide memory allocator, but we want to account allocations against
16// the specific SqliteDatabase on whose behalf they are made, and enforce a hard limit for memory
17// allocations per-database. We do this by:
18//
19// 1. Installing a custom sqlite3_mem_methods that wraps the system allocator.
20//
21// 2. SqliteDatabase wraps every sqlite3_* entry point that may allocate or deallocate memory with
22// a stack-allocated SqliteMemoryScope. The scope points at a size_t byte counter owned by
23// SqliteDatabase to meter memory allocations.
24//
25// 3. When a SqliteMemoryScope is active on the current thread, we count each memory allocation
26// against the byte counter and enforce the per-database hard limit by returning nullptr, signalling
27// SQLite to throw a SQLITE_NOMEM exception.
28//
29// SqliteMemoryScope is idempotent: if a scope is already active on the current thread, additional
30// scopes are no-ops. This allows safe nesting (e.g., when a column accessor calls into SQLite while
31// a Query::nextRow() scope is already active).
32class SqliteMemoryScope {
33 public:
34 // memoryBytes: the per-database byte counter owned by SqliteDatabase for its lifetime.
35 // maxMemoryBytes: the per-database cap from WorkerLimits::sqliteMaxMemoryMb.
36 //
37 // If a scope is already active on this thread, this constructor is a no-op (idempotent).
38 explicit SqliteMemoryScope(size_t& memoryBytes, size_t maxMemoryBytes);
39 
40 ~SqliteMemoryScope() noexcept(false);
41 KJ_DISALLOW_COPY_AND_MOVE(SqliteMemoryScope);
42 
43 private:
44 // These are accessed by the allocator functions via threadLocalScope.
45 size_t& memoryBytes;
46 const size_t maxMemoryBytes;
47 
48 // Thread-local pointer to the active scope. Set to this on construction (if not already set),
49 // cleared on destruction (if we set it).
50 static thread_local SqliteMemoryScope* threadLocalScope;
51 
52 friend void* sqliteMemMalloc(int);
53 friend void sqliteMemFree(void*);
54 friend void* sqliteMemRealloc(void*, int);
55};
56 
57// The custom sqlite3_mem_methods functions. Declared here so tests can call them directly to
58// verify accounting behaviour without going through the SQLite query engine.
59void* sqliteMemMalloc(int size);
60void sqliteMemFree(void* ptr);
61void* sqliteMemRealloc(void* ptr, int newSize);
62 
63// Install the custom sqlite3_mem_methods.
64//
65// This must be called before the first sqlite3_initialize(), sqlite3_open_v2(), or
66// sqlite3_vfs_register() call in the process. Idempotent (uses a static once-flag).
67void installSqliteCustomAllocator();
68 
69} // namespace workerd