Skip to content

Fix: fork-after-JVM-start detection + experimental dynamic classpath loading#72

Merged
HenryNebula merged 4 commits intodevfrom
worktree-triage+71-jpype-classloader
Apr 30, 2026
Merged

Fix: fork-after-JVM-start detection + experimental dynamic classpath loading#72
HenryNebula merged 4 commits intodevfrom
worktree-triage+71-jpype-classloader

Conversation

@HenryNebula
Copy link
Copy Markdown
Owner

@HenryNebula HenryNebula commented Apr 24, 2026

Summary

Fixes #71: JPype 'already loaded in another classloader' error in forked processes (Gunicorn pre-fork model), and adds experimental dynamic classpath loading to bypass the fork guard entirely.

Changes

  • Track the PID when the JVM starts via _jvm_started_pid
  • On subsequent connect() calls, check if PID changed (indicating a fork)
  • Raise InterfaceError with actionable guidance instead of letting JPype fail with a cryptic UnsatisfiedLinkError
  • New experimental feature: connect(..., experimental={'dynamic_classpath': True}) loads JDBC drivers from JARs after JVM start using the DriverShim pattern (URLClassLoader + JPype @JImplements proxy for java.sql.Driver). This bypasses the fork guard, enabling use in gunicorn --preload workers where each worker can load its own drivers post-fork.

Test plan

  • Added DynamicClasspathTest — dynamic load after JVM start, fork guard bypass, without-flag behavior
  • Added DynamicClasspathIntegrationTest — dynamically loads HSQLDB driver after JVM already running with mock driver
  • Added ForkSafetyTest with HSQLDB driver
  • All 114 mock tests pass
  • All 41 HSQLDB integration tests pass

Closes #71

Generated with Claude Code

JPype does not support fork after JVM start — the native library state
is corrupted in child processes, causing 'already loaded in another
classloader' errors in Gunicorn and similar pre-fork servers. Track the
PID at JVM start time and raise InterfaceError with actionable guidance
if a forked process attempts to connect.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@HenryNebula HenryNebula force-pushed the worktree-triage+71-jpype-classloader branch from f275328 to ee797be Compare April 28, 2026 13:08
HenryNebula and others added 2 commits April 28, 2026 10:04
Allow loading JDBC drivers from JARs after the JVM has already started,
using a DriverShim proxy (java.sql.Driver implemented via JPype
@JImplements) that delegates to a driver loaded through URLClassLoader.

This bypasses the fork-after-JVM-start guard, enabling use in gunicorn
--preload workers. Opt in with experimental={'dynamic_classpath': True}.

Closes #71

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
These files are build artifacts that were previously committed. They
are now in .gitignore and removed from tracking.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@HenryNebula HenryNebula changed the title Fix: detect fork after JVM start and raise clear error (legacy #232) Fix: fork-after-JVM-start detection + experimental dynamic classpath loading Apr 28, 2026
- test_hsqldb_fails_without_dynamic_classpath: verifies HSQLDB driver
  is NOT available when JVM starts with only mock driver on classpath
- test_dynamic_load_hsqldb_after_jvm_start: starts JVM with mock-only
  classpath, confirms HSQLDB fails first, then loads it dynamically and
  runs real SQL (CREATE TABLE, INSERT, SELECT, DROP)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@HenryNebula HenryNebula merged commit 1c01460 into dev Apr 30, 2026
16 checks passed
@HenryNebula HenryNebula deleted the worktree-triage+71-jpype-classloader branch April 30, 2026 00:54
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