vpelletier / python-functionfs

Pythonic API for linux's functionfs
GNU General Public License v3.0
40 stars 13 forks source link

Shutdown from another thread/task within the main process? #17

Closed dogtopus closed 3 years ago

dogtopus commented 3 years ago

From what I saw currently there's no API that shuts down the gadget from within the main process. The subprocess objects made by the GadgetSubprocessManager are also buried really deep in a convoluted data structure with name mangling (e.g. function_worker = gadget._Gadget__config_list[0][1]['function_list'][0][1]), which makes it ugly to manually look for these subprocesses and send SIGTERM/SIGINT to them in order to shut them down (and it also upsets pylint).

dogtopus commented 3 years ago

Turns out the waitForever just sits there and wait for another signal and the shutdown operation is performed when the gadget context exits. So I ended up just turn the "another thread" into the main thread and it works pretty well.

dogtopus commented 3 years ago

In the end it doesn't even matter that much since I moved everything to the gadget subprocess, including shutting down itself when needed.

vpelletier commented 3 years ago

When exiting the context managed by an instance of the Gadget class (here, GadgetSubprocessManager), it will stop all functions. Which means killing subprocesses for functions which are subprocesses. It does so after detaching itself from the UDC so the host sees a clear disconnect and not a partially unresponsive device.

Do you have a use-case where you need to keep the gadget up and connected to the host but stop some of its functions ? My impression is that it would not be a normal device behaviour (from host point of view some endpoints would "vanish" while still present in the descriptors, because the descriptors are not updated while the device is connected).

dogtopus commented 3 years ago

Do you have a use-case where you need to keep the gadget up and connected to the host but stop some of its functions ? My impression is that it would not be a normal device behaviour (from host point of view some endpoints would "vanish" while still present in the descriptors, because the descriptors are not updated while the device is connected).

No I don't have such use case. What I want to achieve is to have some sort of "disconnect command" on device side so the whole gadget process tree (including all of its subprocesses at once) can be shut down independently from host by some triggers other than signal (like an "exit" command on an interactive console) and I know that will certainly reset the USB line but that's part of the intention (shutdown the gadget and disconnect from the host, while resetting the USB line like a real physical unplug).

Currently I only have 1 function subprocess so I just have the console on that subprocess and send SIGINT to the process itself when I type "exit" and it seems to work fine.

vpelletier commented 3 years ago

A way to do that is to make the function process exit.

If ConfigFunctionFFS.__init__'s no_disconnect argument is false (default) and the function closes any endpoint file (which will happen in Function.__exit__, which will also cancel all in-flight I/O requests), the kernel will disconnect the gadget from the UDC.

Then, as the function process exits, the gadget-level parent process will receive SIGCHLD, which will wake GadgetSubprocessManager.waitForever (as it should be sitting there at that time) and raise a KeyboardInterrupt exception (signal.signal(signal.SIGCHLD, _raiseKeyboardInterrupt)), which causes the gadget context manager to exit, which fully winds the gadget down (kill any remaining function, remove configfs nodes). The exception is then swallowed by GadgetSubprocessManager.__exit__.

At this point you regain control at the exit of the context manager, and you can possibly loop back to re-enter the context manager and re-setup the gadget, which will set the gadget up in configfs, spawn function process(es), and attach the gadget to the bus.

dogtopus commented 3 years ago

As previously mentioned, I ended up exiting the function process from itself to implement such shutdown behavior.

To do it on the main process is easy too, just e.g. run the gadget manager context in another thread and block it with a Event.wait() until the event is set.

Close this for now.