https://bugs.kde.org/show_bug.cgi?id=379215
This is fairly depressing. KAuth's polkit/dbus backend has no notion of running the same action more than once at any given point in time. Neither from the same client nor multiple clients. This means the same helper practically cannot be used by the multiple clients (the linked bug), and also cannot run multiple different action-ids at the same time (plasma-disks shoots off multiple SMART queries for all drives).
Fixing it is super involved though. What I would do is:
Helper
Instead of exposing the actions as functions we should scope them into objects. i.e. the clients call org.kde.kf5auth.createAction and get back a dbus object path /Action/123 which then encapsulates that invocation context. This gives us a 1:1 mapping between a client Action and a helper Action which solves most of the reentrancy problem by allowing us to easily scope all the context. Obviously this would need to be opt-in because there is no way of telling if existing code is in fact reentrant :(. The helper-side context object then also allows us to authenticate the request at creation time and from then on out scope all dbus traffic to the authenticated remote (as opposed to right now where we broadcast all signals).
Client
Client-side API would not change since Actions are already jobs there. Internally we would need to rejigger some things inside the Action but for the most part I think we only need to give out a dbus interface wrapper from the proxyhelper and then connect to that instead of the helper. Thus embracing the 1:1 mapping with the helper action.
Concurrency
On its own the dbus changes don't really help much though. If multiple actions are being run at the same time that either means we need to remove all timeouts (which may be a reasonable thing to do in the interest of simplicity) and have all actions run sequentially, but neatly separated.
To achieve concurrency I don't think we'll be able to avoid threading, unless we want to fork off each Action into its own anonymous instance. I'm not sure that is necessarily simpler though.
Fork away
createAction would authenticate the request and then fork itself to create a specific instance for the authenticated request. Instead of giving out an object path we'd give out the new serviceName (e.g. :1.234). The client would then connect to that helper instance and that helper will only ever talk to that client. The main helper would also have to keep track of the forked off instances somehow (service watcher?) to detect when idle and auto-terminate.
Threading
Runnable/Function/Future
First option for threading would be to simply have the helpers opt into threading and when that is the case we assume all functions are thread-safe. We can then simply run them in the threadpool. Since the main helper is still in the driver seat, detecting idleness for auto-termination should be easy.
Objectivy
The alternative would be to go full on OOP on the problem and have the helper be an ActionFactory that creates Action objects and we then moveTo the Actions in an own thread each. This may be a bit overblown since I expect most helper actions are fairly straight forward functions and if not one could still objectivy stuff by blocking the action function with an eventloop.