@@ -39,21 +39,22 @@ public final class JavaVirtualMachine: @unchecked Sendable {
3939
4040 /// Thread-local storage to detach from thread on exit
4141 private static let destroyTLS = ThreadLocalStorage { _ in
42+ debug ( " Run destroyThreadLocalStorage; call JVM.shared() detach current thread " )
4243 try ? JavaVirtualMachine . shared ( ) . detachCurrentThread ( )
4344 }
4445
4546 /// The Java virtual machine instance.
4647 private let jvm : JavaVMPointer
4748
48- let classpath : [ String ]
49+ let classpath : [ String ] ?
4950
5051 /// Whether to destroy the JVM on deinit.
5152 private let destroyOnDeinit : LockedState < Bool > // FIXME: we should require macOS 15 and then use Synchronization
5253
5354 /// Adopt an existing JVM pointer.
54- public init ( adoptingJVM jvm: JavaVMPointer ) {
55+ public init ( adoptingJVM jvm: JavaVMPointer , classpath : [ String ] ? = nil ) {
5556 self . jvm = jvm
56- self . classpath = [ ] // FIXME: bad...
57+ self . classpath = nil
5758 self . destroyOnDeinit = . init( initialState: false )
5859 }
5960
@@ -86,7 +87,7 @@ public final class JavaVirtualMachine: @unchecked Sendable {
8687 for path in classpath {
8788 if !fileManager. fileExists ( atPath: path) {
8889 // FIXME: this should be configurable, a classpath missing a directory isn't reason to blow up
89- print ( " [warning][swift-java][JavaVirtualMachine ] Missing classpath element: \( URL ( fileURLWithPath: path) . absoluteString) " ) // TODO: stderr
90+ debug ( " [warning] Missing classpath element: \( URL ( fileURLWithPath: path) . absoluteString) " ) // TODO: stderr
9091 }
9192 }
9293 let pathSeparatedClassPath = classpath. joined ( separator: FileManager . pathSeparator)
@@ -116,7 +117,10 @@ public final class JavaVirtualMachine: @unchecked Sendable {
116117 vmArgs. options = optionsBuffer. baseAddress
117118 vmArgs. nOptions = jint ( optionsBuffer. count)
118119
119- // Create the JVM instance.
120+ debug ( " Create JVM instance. Options: \( allVMOptions) " )
121+ debug ( " Create JVM instance. jvm: \( jvm) " )
122+ debug ( " Create JVM instance. environment: \( environment) " )
123+ debug ( " Create JVM instance. vmArgs: \( vmArgs) " )
120124 if let createError = VMError ( fromJNIError: JNI_CreateJavaVM ( & jvm, & environment, & vmArgs) ) {
121125 throw createError
122126 }
@@ -126,8 +130,10 @@ public final class JavaVirtualMachine: @unchecked Sendable {
126130 }
127131
128132 public func destroyJVM( ) throws {
133+ debug ( " Destroy jvm (jvm: \( jvm) ) " )
129134 try self . detachCurrentThread ( )
130- if let error = VMError ( fromJNIError: jvm. pointee!. pointee. DestroyJavaVM ( jvm) ) {
135+ let destroyResult = jvm. pointee!. pointee. DestroyJavaVM ( jvm)
136+ if let error = VMError ( fromJNIError: destroyResult) {
131137 throw error
132138 }
133139
@@ -151,9 +157,33 @@ extension JavaVirtualMachine: CustomStringConvertible {
151157 }
152158}
153159
160+ let SwiftJavaVerboseLogging = {
161+ if let str = ProcessInfo . processInfo. environment [ " SWIFT_JAVA_VERBOSE " ] {
162+ switch str. lowercased ( ) {
163+ case " true " , " yes " , " 1 " : true
164+ case " false " , " no " , " 0 " : false
165+ default : false
166+ }
167+ } else {
168+ false
169+ }
170+ } ( )
171+
172+ fileprivate func debug( _ message: String , file: String = #fileID, line: Int = #line, function: String = #function) {
173+ if SwiftJavaVerboseLogging {
174+ print ( " [swift-java-jvm][t: \( getCurrentThreadID ( ) ) ][ \( file) : \( line) ]( \( function) ) \( message) " )
175+ }
176+ }
177+
154178// ==== ------------------------------------------------------------------------
155179// MARK: Java thread management.
156180
181+ fileprivate func getCurrentThreadID( ) -> UInt64 {
182+ var threadID : UInt64 = 0
183+ pthread_threadid_np ( nil , & threadID)
184+ return threadID
185+ }
186+
157187extension JavaVirtualMachine {
158188 /// Produce the JNI environment for the active thread, attaching this
159189 /// thread to the JVM if it isn't already.
@@ -162,6 +192,7 @@ extension JavaVirtualMachine {
162192 /// - asDaemon: Whether this thread should be treated as a daemon
163193 /// thread in the Java Virtual Machine.
164194 public func environment( asDaemon: Bool = false ) throws -> JNIEnvironment {
195+ debug ( " Get JVM env, asDaemon: \( asDaemon) " )
165196 // Check whether this thread is already attached. If so, return the
166197 // corresponding environment.
167198 var environment : UnsafeMutableRawPointer ? = nil
@@ -205,6 +236,7 @@ extension JavaVirtualMachine {
205236 /// Detach the current thread from the Java Virtual Machine. All Java
206237 /// threads waiting for this thread to die are notified.
207238 func detachCurrentThread( ) throws {
239+ debug ( " Detach current thread, jvm: \( jvm) " )
208240 if let resultError = VMError ( fromJNIError: jvm. pointee!. pointee. DetachCurrentThread ( jvm) ) {
209241 throw resultError
210242 }
@@ -227,6 +259,17 @@ extension JavaVirtualMachine {
227259 /// simple.
228260 private static let sharedJVM : LockedState < JVMState > = . init( initialState: . init( jvm: nil , classpath: [ ] ) )
229261
262+ public static func destroySharedJVM( ) throws {
263+ debug ( " Destroy shared JVM " )
264+ return try sharedJVM. withLock { ( sharedJVMPointer: inout JVMState ) in
265+ if let jvm = sharedJVMPointer. jvm {
266+ try jvm. destroyJVM ( )
267+ }
268+ sharedJVMPointer. jvm = nil
269+ sharedJVMPointer. classpath = [ ]
270+ }
271+ }
272+
230273 /// Access the shared Java Virtual Machine instance.
231274 ///
232275 /// If there is no shared Java Virtual Machine, create one with the given
@@ -252,23 +295,26 @@ extension JavaVirtualMachine {
252295 file: String = #fileID, line: Int = #line
253296 ) throws -> JavaVirtualMachine {
254297 precondition ( !classpath. contains ( where: { $0. contains ( FileManager . pathSeparator) } ) , " Classpath element must not contain ` \( FileManager . pathSeparator) `! Split the path into elements! Was: \( classpath) " )
255- print ( " [swift] Get shared JVM at \( file) : \( line) : Classpath = \( classpath. joined ( separator: " , " ) ) " )
298+ debug ( " Get shared JVM at \( file) : \( line) : Classpath = \( classpath. joined ( separator: FileManager . pathSeparator ) ) " )
256299
257300 return try sharedJVM. withLock { ( sharedJVMPointer: inout JVMState ) in
258301 // If we already have a JavaVirtualMachine instance, return it.
259302 if replace {
260- print ( " [swift] Replace JVM instance" )
303+ debug ( " Replace JVM instance " )
261304 if let jvm = sharedJVMPointer. jvm {
262- print ( " [swift] destroyJVM instance!" )
305+ debug ( " destroyJVM instance! " )
263306 try jvm. destroyJVM ( )
264- print ( " [swift] destroyJVM instance, done." )
307+ debug ( " destroyJVM instance, done. " )
265308 }
266309 sharedJVMPointer. jvm = nil
267310 sharedJVMPointer. classpath = [ ]
268311 } else {
269312 if let existingInstance = sharedJVMPointer. jvm {
270- if classpath != sharedJVMPointer. classpath {
271- print ( " [swift] Return existing JVM instance, same classpath classpath. " )
313+ if classpath == [ ] {
314+ debug ( " Return existing JVM instance, no classpath requirement. " )
315+ return existingInstance
316+ } else if classpath != sharedJVMPointer. classpath {
317+ debug ( " Return existing JVM instance, same classpath classpath. " )
272318 return existingInstance
273319 } else {
274320 fatalError (
@@ -281,50 +327,88 @@ extension JavaVirtualMachine {
281327 }
282328 }
283329
330+ var remainingRetries = 8
284331 while true {
332+ remainingRetries -= 1
333+ guard remainingRetries > 0 else {
334+ fatalError ( " Unable to find or create JVM " )
335+ }
336+
285337 var wasExistingVM : Bool = false
286338 while true {
339+ remainingRetries -= 1
340+ guard remainingRetries > 0 else {
341+ fatalError ( " Unable to find or create JVM " )
342+ }
343+
287344 // Query the JVM itself to determine whether there is a JVM
288- // instance that we don't yet know about.
289- var jvm : UnsafeMutablePointer < JavaVM ? > ? = nil
345+ // instance that we don't yet know about.©
290346 var numJVMs : jsize = 0
291347 if JNI_GetCreatedJavaVMs ( nil , 0 , & numJVMs) == JNI_OK, numJVMs == 0 {
292- print ( " [swift] Found JVMs: \( numJVMs) , return existing one " )
348+ debug ( " Found JVMs: \( numJVMs) , create new one " )
349+ } else {
350+ debug ( " Found JVMs: \( numJVMs) , get existing one... " )
293351 }
294352
295- if JNI_GetCreatedJavaVMs ( & jvm, 1 , & numJVMs) == JNI_OK, numJVMs >= 1 {
296- print ( " [swift] Found JVMs: \( numJVMs) , return existing one " )
297- // Adopt this JVM into a new instance of the JavaVirtualMachine
298- // wrapper.
299- // FIXME: account for classpath
300- let javaVirtualMachine = JavaVirtualMachine ( adoptingJVM: jvm!)
301- sharedJVMPointer. jvm = javaVirtualMachine
302- sharedJVMPointer. classpath = classpath
303- return javaVirtualMachine
353+ // Allocate buffer to retrieve existing JVM instances
354+ // Only allocate if we actually have JVMs to query
355+ if numJVMs > 0 {
356+ let bufferCapacity = Int ( numJVMs)
357+ let jvmInstancesBuffer = UnsafeMutableBufferPointer< JavaVM?> . allocate( capacity: bufferCapacity)
358+ defer {
359+ jvmInstancesBuffer. deallocate ( )
360+ }
361+
362+ // Query existing JVM instances with proper error handling
363+ var jvmBufferPointer = jvmInstancesBuffer. baseAddress
364+ let jvmQueryResult = JNI_GetCreatedJavaVMs ( & jvmBufferPointer, numJVMs, & numJVMs)
365+
366+ // Handle query result with comprehensive error checking
367+ guard jvmQueryResult == JNI_OK else {
368+ if let queryError = VMError ( fromJNIError: jvmQueryResult) {
369+ debug ( " Failed to query existing JVMs: \( queryError) " )
370+ throw queryError
371+ }
372+ fatalError ( " Unknown error querying JVMs, result code: \( jvmQueryResult) " )
373+ }
374+
375+ if numJVMs >= 1 {
376+ debug ( " Found JVMs: \( numJVMs) , try to adopt existing one " )
377+ // Adopt this JVM into a new instance of the JavaVirtualMachine wrapper.
378+ let javaVirtualMachine = JavaVirtualMachine (
379+ adoptingJVM: jvmInstancesBuffer. baseAddress!,
380+ classpath: classpath
381+ )
382+ sharedJVMPointer. jvm = javaVirtualMachine
383+ sharedJVMPointer. classpath = classpath
384+ return javaVirtualMachine
385+ }
386+
387+ precondition (
388+ !wasExistingVM,
389+ " JVM reports that an instance of the JVM was already created, but we didn't see it. "
390+ )
304391 }
305392
306- precondition (
307- !wasExistingVM,
308- " JVM reports that an instance of the JVM was already created, but we didn't see it. "
309- )
310-
311393 // Create a new instance of the JVM.
312- print ( " [swift] Create JVM" )
394+ debug ( " Create JVM, classpath: \( classpath . joined ( separator : FileManager . pathSeparator ) ) " )
313395 let javaVirtualMachine : JavaVirtualMachine
314396 do {
315397 javaVirtualMachine = try JavaVirtualMachine (
316398 classpath: classpath,
317- vmOptions: vmOptions,
399+ vmOptions: vmOptions, // + ["-verbose:jni"],
318400 ignoreUnrecognized: ignoreUnrecognized
319401 )
320402 } catch VMError . existingVM {
321403 // We raced with code outside of this JavaVirtualMachine instance
322404 // that created a VM while we were trying to do the same. Go
323405 // through the loop again to pick up the underlying JVM pointer.
406+ debug ( " Failed to create JVM, Existing VM! " )
324407 wasExistingVM = true
325408 continue
326409 }
327410
411+ debug ( " Created JVM: \( javaVirtualMachine) " )
328412 sharedJVMPointer. jvm = javaVirtualMachine
329413 sharedJVMPointer. classpath = classpath
330414 return javaVirtualMachine
@@ -337,6 +421,7 @@ extension JavaVirtualMachine {
337421 ///
338422 /// This will allow the shared JavaVirtualMachine instance to be deallocated.
339423 public static func forgetShared( ) {
424+ debug ( " forget shared JVM, without destroying it " )
340425 sharedJVM. withLock { sharedJVMPointer in
341426 sharedJVMPointer. jvm = nil
342427 sharedJVMPointer. classpath = [ ]
0 commit comments