diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 3a701bf74..885ccc689 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -75,6 +75,18 @@ pub fn create_app() -> tauri::Builder { }); } + // WebKitGTK's WASM optimizing JIT tier (OMG) leaks native memory when repeatedly + // recompiling wa-sqlite's WASM module (used via IDBBatchAtomicVFS, PowerSync's OPFS + // fallback on this platform - see getPowerSyncOptions in src/db/powersync/database.ts), + // eventually OOM-killing the WebKitWebProcess. Disabling just the OMG tier (baseline + // JIT stays on) avoids it. Must be set before the webview is created, since WebKitGTK + // reads JSC_* env vars at its own init time. Remove once upstream WebKit fixes this + // (tracked at https://bugs.webkit.org/show_bug.cgi?id=319572). + #[cfg(target_os = "linux")] + { + std::env::set_var("JSC_useOMGJIT", "false"); + } + builder } diff --git a/src/db/powersync/database.test.ts b/src/db/powersync/database.test.ts index ddad02614..07c845be1 100644 --- a/src/db/powersync/database.test.ts +++ b/src/db/powersync/database.test.ts @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { describe, expect, it } from 'bun:test' -import { WASQLiteOpenFactory } from '@powersync/web' +import { WASQLiteOpenFactory, WASQLiteVFS } from '@powersync/web' const { getPowerSyncDatabaseConfig, getPowerSyncOptions } = await import('./database') @@ -101,5 +101,26 @@ describe('getPowerSyncOptions', () => { const factory = options.database as WASQLiteOpenFactory expect(factory.waOptions.dbFilename).toBe('thunderbolt-sync.db') }) + + it('uses OPFSCoopSyncVFS when OPFS is available (default)', () => { + const options = getPowerSyncOptions('thunderbolt-sync.db', 'safari-tauri') + const factory = options.database as WASQLiteOpenFactory + expect(factory.waOptions.vfs).toBe(WASQLiteVFS.OPFSCoopSyncVFS) + }) + + it('falls back to IDBBatchAtomicVFS when OPFS is unavailable', () => { + const options = getPowerSyncOptions('thunderbolt-sync.db', 'safari-tauri', false) + const factory = options.database as WASQLiteOpenFactory + expect(factory.waOptions.vfs).toBe(WASQLiteVFS.IDBBatchAtomicVFS) + }) + + it('keeps Tauri-safe worker wiring in the OPFS-unavailable fallback', () => { + const options = getPowerSyncOptions('thunderbolt-sync.db', 'safari-tauri', false) + const factory = options.database as WASQLiteOpenFactory + expect(factory.waOptions.worker).toBe('/@powersync/worker/WASQLiteDB.umd.js') + expect(factory.waOptions.flags?.enableMultiTabs).toBe(false) + expect('flags' in options && options.flags).toEqual({ enableMultiTabs: false }) + expect(options.sync).toEqual({ worker: '/@powersync/worker/SharedSyncImplementation.umd.js' }) + }) }) }) diff --git a/src/db/powersync/database.ts b/src/db/powersync/database.ts index f17df8015..30a06329d 100644 --- a/src/db/powersync/database.ts +++ b/src/db/powersync/database.ts @@ -56,8 +56,16 @@ const initialSyncTimeoutMs = 10_000 */ const initialSyncPriority = 1 -/** @internal Exported for testing */ -export const getPowerSyncOptions = (path: string, config: PowerSyncDatabaseConfig = getPowerSyncDatabaseConfig()) => { +/** + * @internal Exported for testing + * @param opfsAvailable - Whether OPFS (`navigator.storage.getDirectory`) is actually usable on this + * platform. Only affects the 'safari-tauri' config's VFS choice — see the comment below. + */ +export const getPowerSyncOptions = ( + path: string, + config: PowerSyncDatabaseConfig = getPowerSyncDatabaseConfig(), + opfsAvailable = true, +) => { const dbFilename = path.includes('/') ? path.split('/').pop() || 'thunderbolt.db' : path if (config === 'default') { @@ -87,12 +95,16 @@ export const getPowerSyncOptions = (path: string, config: PowerSyncDatabaseConfi * Explicit UMD worker paths — bypasses import.meta.url which fails under tauri://. * enableMultiTabs: false — dedicated worker, not SharedWorker (fails under tauri://). * + * Falls back to IDBBatchAtomicVFS (IndexedDB-backed, no OPFS dependency) when OPFS itself + * is unavailable — e.g. WebKitGTK (Tauri on Linux) doesn't implement `navigator.storage.getDirectory`, + * so OPFSCoopSyncVFS's worker throws on init instead of ever reaching this Asyncify tradeoff. + * * Docs: https://docs.powersync.com/debugging/troubleshooting#common-issues */ return { database: new WASQLiteOpenFactory({ dbFilename: dbFilename, - vfs: WASQLiteVFS.OPFSCoopSyncVFS, + vfs: opfsAvailable ? WASQLiteVFS.OPFSCoopSyncVFS : WASQLiteVFS.IDBBatchAtomicVFS, worker: '/@powersync/worker/WASQLiteDB.umd.js', flags: { enableMultiTabs: false }, }), @@ -140,7 +152,10 @@ export class PowerSyncDatabaseImpl implements DatabaseInterface { return // Already initialized } - const options = getPowerSyncOptions(path) + // `getDatabasePath` (called before `initialize`) already probes real OPFS availability and + // returns the ':memory:' sentinel when it's unavailable — reuse that instead of re-probing. + const opfsAvailable = path !== ':memory:' + const options = getPowerSyncOptions(path, getPowerSyncDatabaseConfig(), opfsAvailable) // Always use ThunderboltPowerSyncDatabase with TransformableBucketStorage + encryption middleware. // The middleware is data-driven (checks __enc: prefix), so it's a no-op when E2EE is disabled.