Is it possible to run a Java .jar as a public XPC Service available to all Applications?

I apologize for attempting this monstrosity, but... It is forced on me.

I have a .jar implementing logic that I receive prebuilt. This .jar exports some API you can use (... in Java...) to request information about file-system items you hand in as paths.

This .jar is compatible with Java 11, and runs with the "Zulu" Java VM.

I need to "wrap" it in some way that will behave like a global daemon, launched and managed by launchd, available to other components (apps, user-agents and global-daemons) in our product.

Running it as a global daemon is a breeze - simply place a .plist in /Library/LaunchDaemons/myMonster.plist and have the java -jar <path to my .jar> arguments, throw in a 'keep alive' option, and it runs.

Now... It makes sense for other components to pass it "queries" and receive "results" via XPC connection. First, because this way I could deny connection to unknown components, I could have a secure connection, and also integrate nicely from the other components ObjC code.

However... XPC isn't something available in JDK, and the actual executable launched is the Zulu java command binary of course, that I can't modify.

So... I tried to use JNA (Java Native Access) and (with much tears and sweat) get my java code to create an xpc connection (as client! not "service") to another XPC Service already running.

Also, I was only able to do it via xpc.h (the old C API. not ObjC NSXPCConnection as of yet).

The documentation on old C-style XPC Services is very thin, almost nothing... and the only thing about Creating an XPC Service of this style says:

// If you are writing a low-level (C-based) XPC service, implement a minimal main function to register your event handler, as shown in the following code listing. Replace my_event_handler with the name of your event handler function.
int main(int argc, const char *argv[]) {
    xpc_main(my_event_handler);
 
    // The xpc_main() function never returns.
    exit(EXIT_FAILURE);
}

Which of course, I can't do! I don't control the process 'main()' entry point (it's java...) and I can't block anything.

So here's my question:

  1. Where in the lifecycle of a Java program can I call on my native code to set up The XPC Service?
  2. Is there a non-blocking alternative to xpc_main(my_event_handler) I can use to start my service?

I know this calls for multi-disciplinary knowledge but any hint or idea will be greatly appreciated.

Thanks!

Answered by DTS Engineer in 697667022

I apologize for attempting this monstrosity

Trust me, I’ve seen worse (-:

Where in the lifecycle of a Java program can I call on my native code to set up the XPC Service?

You are mixing up XPC Service and XPC service (-: Yeah, that’s confusing. Let me explain:

  • An XPC Service is bundle that contains an executable that’s managed by launchd. It vends services to clients as an XPC service.

  • An XPC service is a Mach service that speaks the XPC protocol.

Note This is a convention that I use, not something that’s well-defined by Apple. One day I’ll get this sorted out in the official documentation, but today is not that day )-:

When you set a MachServices property is a launchd property list, you’re creating a launchd daemon that advertises an XPC service but it is not an XPC Service; it’s still a launchd daemon. Given that, it is not allowed to call xpc_main.

A dedicated macOS daemon would instead call dispatch_main. In your case, however, your third-party runtime is using some other main event loop (based on select, or kqueues, or whatever) and that’s fine. A launchd daemon has that flexibility.

Is there a non-blocking alternative to xpc_main(my_event_handler) I can use to start my service?

No, because that doesn’t make sense in this context. Rather, your XPC subsystem should ‘check in’ with launchd using a completely different mechanism, that is, xpc_connection_create_mach_service with the XPC_CONNECTION_MACH_SERVICE_LISTENER option. That returns a listener connection, configured to listen on that Mach service name. When a client creates a connection to that listener, your event handler is called as discussed in the doc comments for xpc_connection_set_event_handler.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Accepted Answer

I apologize for attempting this monstrosity

Trust me, I’ve seen worse (-:

Where in the lifecycle of a Java program can I call on my native code to set up the XPC Service?

You are mixing up XPC Service and XPC service (-: Yeah, that’s confusing. Let me explain:

  • An XPC Service is bundle that contains an executable that’s managed by launchd. It vends services to clients as an XPC service.

  • An XPC service is a Mach service that speaks the XPC protocol.

Note This is a convention that I use, not something that’s well-defined by Apple. One day I’ll get this sorted out in the official documentation, but today is not that day )-:

When you set a MachServices property is a launchd property list, you’re creating a launchd daemon that advertises an XPC service but it is not an XPC Service; it’s still a launchd daemon. Given that, it is not allowed to call xpc_main.

A dedicated macOS daemon would instead call dispatch_main. In your case, however, your third-party runtime is using some other main event loop (based on select, or kqueues, or whatever) and that’s fine. A launchd daemon has that flexibility.

Is there a non-blocking alternative to xpc_main(my_event_handler) I can use to start my service?

No, because that doesn’t make sense in this context. Rather, your XPC subsystem should ‘check in’ with launchd using a completely different mechanism, that is, xpc_connection_create_mach_service with the XPC_CONNECTION_MACH_SERVICE_LISTENER option. That returns a listener connection, configured to listen on that Mach service name. When a client creates a connection to that listener, your event handler is called as discussed in the doc comments for xpc_connection_set_event_handler.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Thank you very much for this clear and useful answer.

Making it an "XPC service" - launchd daemon "Advertising" a public XPC service is probably the right way for me.

I'm trying to make it work this way (still doesn't work and clients don't seem to "find" me) ... still struggling.

However... for theoretical completeness, if I wanted to pack this monster as an XPC Service (capital S) inside an App's bundle, what then? Is there a way to overcome the inability to call xpc_main(my_event_handler) ? I'm asking purely for understanding the mechanism, and maybe... some day... I'll be able to persuade managers to make the whole product a real Mac App - and so gain a zillion and one benefits.

I'm trying to make it work this way (still doesn't work and clients don't seem to "find" me) ... still struggling.

The one ‘obvious’ gotcha here is that, if you register the launchd job as a daemon then you have to set XPC_CONNECTION_MACH_SERVICE_PRIVILEGED on the client XPC connection. OTOH, if you register it as an agent, then you have to not set that.

Oh, and if you’re using the old school launchctl interface for loading the job to test — and, to be clear, that’s the interface I use most of the time because it makes it easy to do the standard stuff — be aware that the load command determines this based on whether you run it as root (daemon) or not (agent in current context).

However... for theoretical completeness, if I wanted to pack this monster as an XPC Service (capital S) inside an App's bundle, what then?

Some random thoughts:

  • An XPC Service is only available to the app that contains it. So making this change would preclude the whole “available to other components” thing.

  • One way around that is to use a Service Management login item, as installed by SMLoginItemSetEnabled. Its service is available to any code running in that user context [1].

  • You can use the RunLoopType property to configure the type of event loop used by the XPC Service. See the xpcservice.plist man page for details.

  • You might want to look into solutions for this in the Java space. I can’t imagine this hasn’t come up before over there (that is, the ability to spin up a Java VM that doesn’t block the main thread).

  • On the XPC side of things, I’ve helped developers with similar issues before. If you get serious about this, open a DTS tech support incident and we can discuss your options.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] Subject to sandbox constraints at the client end.

What I'd like to do … is to restructure all components into a single bundle

Ooo, ooo, do I have the doc for you: Embedding Nonstandard Code Structures in a Bundle.

In that respect, I'd like to have this Java-written "service" be an XPC-Service available to other components WITHIN its bundle. Is this possible?

Not really. Currently, for a third-party XPC Service, we only support it in an app, and that service is only visible to the app itself. If you want your XPC service to be available to a wider range of code, you’ll need a launchd daemon, a launchd agent, or a Service Management login item.

Having said that, it probably still makes sense to sort out your bundle structure just to help with code signing and so on.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Is it possible to run a Java .jar as a public XPC Service available to all Applications?
 
 
Q