Skip to content

perf(plugins): defer bundle.load() until first connection or format use#1024

Merged
datlechin merged 1 commit intomainfrom
perf/lazy-plugin-loading
May 6, 2026
Merged

perf(plugins): defer bundle.load() until first connection or format use#1024
datlechin merged 1 commit intomainfrom
perf/lazy-plugin-loading

Conversation

@datlechin
Copy link
Copy Markdown
Member

Summary

  • Welcome window: 0 plugin binaries loaded at launch (verified via vmmap); footprint 47.9 MB (PR A baseline) → 44.9 MB
  • Plugins are discovered at startup via Info.plist only (no bundle.load()). The bundle's executable code is loaded on demand when a connection of that driver type is opened, an export format is invoked, or an import format is invoked
  • Source: WWDC 2019 Optimizing App Launch"Don't use dynamic library loading (dlopen, NSBundleLoad)" at launch. Apple's NSBundle API is designed for this: Bundle(url:) reads metadata, bundle.load() is the explicit gate, bundle.isLoaded is queryable
  • Mirrors how Apple's own bundle-based plugin systems work (.mdimporter, .qlgenerator, .appex): host reads each bundle's Info.plist at registration time and loads code only when the system invokes the plugin

Changes

  • PluginManifest.swift (new) — reads TableProProvidesDatabaseTypeIds / TableProProvidesExportFormatIds / TableProProvidesImportFormatIds from a bundle's Info.plist without loading code
  • 11 built-in plugin Info.plist files — added the TableProProvides* manifest keys (MySQL, PostgreSQL, SQLite, Redis, ClickHouse, SQL/CSV/JSON/XLSX/MQL exports, SQL import)
  • PluginManagerloadPlugins() splits pending URLs into lazy (manifest-bearing) and eager (legacy) buckets. Lazy ones go through the new registerLazyManifest(_:url:source:manifest:) which builds a PluginEntry from manifest + the existing PluginMetadataRegistry defaults, with no bundle.load(). New activateDriver(databaseTypeId:), activateExportFormat(_:), activateImportFormat(_:) methods load the bundle and run the existing registerCapabilities path on first use. Plugins without manifest keys keep the eager path (backwards compat for separately-distributed plugins)
  • PluginManager+RegistrationdriverPlugin(for:), exportPlugin(forFormat:), importPlugin(forFormat:) accessors auto-activate. New allExportPlugins() / allImportPlugins() activate all lazy entries before returning the full list (used by Export/Import dialogs that need to enumerate available formats)
  • PluginManager+ValidationvalidateDependencies() skips bundle.principalClass access for unloaded bundles via bundle.isLoaded guard (without this, dep validation force-loads every plugin)
  • DatabaseDriverDatabaseDriverFactory.createDriver(for:) uses PluginManager.shared.driverPlugin(for:) instead of direct dict access, picking up auto-activation
  • Export/Import service callsites — switched from PluginManager.shared.exportPlugins[id] / importPlugins[id] direct dict access to the activating accessors (exportPlugin(forFormat:), importPlugin(forFormat:)). ExportDialog / ImportDialog use allExportPlugins() / allImportPlugins() to populate format pickers

Backwards compatibility

Plugins without TableProProvides* keys (any user-installed plugin from the registry that hasn't been re-released) keep working unchanged via the eager bundle.load() path. No TableProPluginKitVersion bump is needed since the protocol contract didn't change.

Test plan

  • Cold launch with no connections: 0 plugin binaries loaded (verified via vmmap | grep tableplugin)
  • Footprint at Welcome: 47.9 MB → 44.9 MB
  • Open a MySQL connection: MySQL driver activates on demand, log shows "Activated plugin 'com.TablePro.MySQLDriver' on demand"
  • Open Export dialog: all 5 export formats appear, plugins activate
  • Open Import dialog: SQL import format appears, plugin activates
  • Drop a .tableplugin on Finder: install path still works
  • Disable / re-enable a plugin in Settings → Plugins: still works
  • swiftlint --strict on all changed files: 0 violations
  • xcodebuild Release arm64: BUILD SUCCEEDED

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@datlechin datlechin merged commit 151b96b into main May 6, 2026
2 checks passed
@datlechin datlechin deleted the perf/lazy-plugin-loading branch May 6, 2026 07:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant