-
-
Notifications
You must be signed in to change notification settings - Fork 554
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Select which executable inside an AppImage to run based on argv[0]? #419
Comments
Duplicate of #25, pull requests welcome. |
I think that this is something that AppDir creation tools like linuxdeployqt should implement. It would just bloat the runtime, as it is a totally optional feature. |
@TheAssassin: If the AppImage runtime does not evaluate the argv[0] it was invoked with, there is no way for the content of the directory (like AppRun) to know about it. |
That's not quite true. The AppImage runtime sets the environment variable |
@TheAssassin: The code in runtime.c you linked to reads the content of the softlink /proc/self/exe to fill the APPIMAGE environment variable - and that is a path to the actual AppImage binary file, this would not reflect the name of some soft-link to that binary file (as would argv[0]). |
|
Okay, so this is not a duplicate of #25. |
I wonder how the AppImage handles multiple processes started
Why we care? For our PrusaSlicer application, we are adding a G-code viewer as a standalone application. For technical reasons (plenty of ugly global variables) we have to launch another process for the G-code viewer. Executing a 2nd process is also better from the stability point of view. If one dies, the other survives. |
Hi @bubnikv if I understand it correctly, then you want to launch your main process A which than can launch a secondary process B from within the same AppImage mountpoint. I guess you need to have some kind of a wrapper script that launches A and keeps running and does not exit as long as either process A or process B are still running. As soon as that script exits, the AppImage gets unmounted. Another solution would be that whenever A gets closed, it always kills B. (Like if A and B were the same application - you quit the application, it results in all its windows being closed.) Note that process A needs to construct the path to the executable B relative to itself. It must not launch another instance of the AppImage, because this would result in another mountpoint being used and hence no memory being shared. |
Well, likely they won't. But program space itself shouldn't be the biggest issue with your application, it's just a few MiB. Compared to the (up to) hundreds of MiB a slicer uses for its own purposes, it's negligible.
The AppImage runtime should kill all running processes once the main process dies to avoid such situations. Picking up the idea from @probonopd, you could probably also just write a small wrapper that starts PrusaSlicer and keeps track of all subprocesses launched later on. It can keep the mount point alive until all subprocesses have closed properly. |
Thanks @probonopd @TheAssassin for your comments.
Our monolitic static compiled binary is 120MB big. That is not quite negligible. It is true that the application consumes couple of GB RAM easily.
We don't want to kill the other process. We have discussed the topic internally, we decided to go the KISS way and launch another AppImage instance instead of another instance of the application from the mounted file system. I feel quite ashamed doing that though, the perfectionist in mine hurts badly.
We can do that, but first we don't have the resources to do that for the upcoming release of our product, second we believe that such functionality belongs to the AppImage for obvious reasons. We think that this is an universal solution applicable to any large application of which the user may want to execute multiple instances. I propose the following generic solution, which I believe is a worthy addition to the AppImage concept, even if it was just to improve the case 1) (mounting the filesystem twice for two instances of the same AppImage). I understand your proposal to write a user wrapper, but I think the AppImage runtime shall rather implement such a functionality for performance reasons and for generality. I am proposing an extension to the AppImage runtime / libappimage / squashfuse / libsquash. The proposal is based on the assumption that the fuse_session_loop() exits when its process receives SIGCHLD when its subprocess dies. The proposal is based on the idea of having a single instance of the AppImage runtime running (the primary instance), mounting the squashfs, forking subprocesses and closing first when all the subprocesses die.
The fuse_session_loop() concept would have to be modified to keep a database of subprocesses, so that it dies only when the last SIGCHLD is received. I am not sure about the stdin/stdout/stderr of the processes started remotely. I suppose the calling AppImage runtime may pipe the stdin/stdout/stderr streams to the primary AppImage runtime using Unix sockets or similar means. I suppose it is not possible to pass open file descriptors or unnamed pipes to the server to pass them to a forked process, so the stdin/stdout/stderr would have to be piped to the launched application by the secondary AppImage runtime instance. Thus it would be beneficial to have a "daemon" switch to not do the stdin/stdout/stderr redirection at all. Likely asking the AppImage primary to fork another instance is the safest way. One may in theory call prctl(PR_SET_CHILD_SUBREAPER) on the primary AppImage process, so an application my fork a subprocess (a grandchild), and if the grandchild dies, the AppImage primary will be informed with SIGCHLD. But then the Appimage primary would first have to be informed about the existence of each newly launched grandchild, which would complicate the synchronization badly. It makes sense to add the functionality to the AppImage runtime and not to some wrapper inside the squashfs. If the wrapper was inside the squashfs, then it would have to be mounted to perform the communication with the primary. It may make sense to allow delayed release of the primary if the AppImage is used to pack some often executed binary. Imagine git or a compilator suite. BTW we are producing OSX builds as well. While the architecture is similar to AppImage, I suppose the issue with unmounting the .dmg archive is not that bad because the applications are usually installed by unpacking the content of .dmg into user file system and unmounting of the .dmg is controlled by the user manually. |
Well, these kinds of scenarios are not necessarily covered by the AppImage design goals. AppImages are "one app = one file" conceptually. Doesn't mean you can't use them otherwise, but that's the main use case. |
Thanks @bubnikv. I think we all agree that launching a second AppImage instance is less than ideal. If I understand it correctly, then we need a way to keep a script or binary running until all of its child (and sub-children) processes have exited. (I think there was once an AppImage of KMail that had a script for this... if I am not totally wrong) |
The trivial way would be to have an Or am I thinking too simplistic here? @bubnikv if you can send me what you have in the works so far, I can try writing (and testing) the script. |
This is too simplistic indeed. You ignore the fact that you might have multiple AppImages running in parallel, e.g., different versions or simply different instances of the slicer. |
I am worried about race conditions when the "primary" is closing up while the "secondary" is starting a binary from the already mounted file system. It is a non-trivial task to implement the "primary" - "secondary" synchronization in a bullet proof fashion. I proposed a client / server approach, where the "primary" would have a full control over the life time of the binaries. That is certainly a bullet proof solution, as it solely relies on an operating system support of parent / child process synchronization, which itself is very well tested. The main drawback I see is the input / output redirection. It is likely possible to start the "secondary" directly without having to be a child of the "primary". However, there is no way to reparent a process to a new parent apart from the double fork trick where the abandoned child is reparented to the init process or to a process with PR_SET_CHILD_SUBREAPER set. Therefore if the process is not a child of the "primary", then the "primary" / "secondary" synchronization would have to be implemented in a custom way using the inter-process synchronization primitives, which is error prone thus it needs to be well designed and it should cover all the cases where either party blocks or dies unexpectedly. First the "secondary" would have to tell the "primary" not to close the FUSE file system before starting the "secondary" binary and the "primary" would have to confirm the lock. Then the "secondary" would be started and ideally its process ID would be passed to the "primary" server. Ideally when the secondary is dying, as a last thing it would send a message to the "primary".
The master PrusaSlicer has a menu item "Window->Open new instance", which just executes another instance of the prusa-slicer binary. We don't have anything more advanced. |
By checking
Wouldn't it be sufficient to add a couple of |
I would not sleep well doing that. In my experience, if something could get wrong, it eventually will go wrong if enough of users use it enough of times. This is especially true with computational geometry algorithms as in slicer. I will consult with our resident Linux experts at PrusaSlicer about what would be the most elegant synchronization approach. |
Another idea, maybe we can re-use (parts of) https://linux.die.net/man/1/pstree? I'd like to avoid writing complicated code from scratch and rather use something that has been proven already, since as you say, if something could get wrong, it eventually will go wrong if enough of users use it enough of times. |
Again: it will not work (reliably). If those subprocesses are detached from their parent process (using a double fork, for instance), they might not show up in the process tree of the main process any more. Then you're lost, basically. The best possible approach, however, is something tailored for this specific use case, less work and less trouble. It's not a classic AppImage use case and therefore there's no real reason to change our software and do the effort for them. It just increases complexity on our side for no real reason. They'll have to figure it out themselves in case they need this kind of optimization. A simple process manager that keeps track of all instances (maybe even using some IPC so new, forked child procs can register themselves) should do the trick. |
Is Prusa Slicer using double forks? |
This is the process tree of our current master packed in AppImage, where
the first process started one instance of another PrusaSlicer and one
instance of a new G-code viewer (just a PrusaSlicer in disguise).
```
init
├─init
│ └─init
│ ├─PrusaSlicer-2.3
│ │ └─{PrusaSlicer-2.3}
│ ├─PrusaSlicer-2.3
│ │ └─{PrusaSlicer-2.3}
│ ├─PrusaSlicer-2.3 --gcodeviewer
/mnt/c/Users/bubni/Downloads/3DBenchy_0.15mm_PLA_MINI_2h0m.gcode
│ │ └─{PrusaSlicer-2.3}
│ ├─zsh
│ │ ├─prusa-slicer
│ │ │ ├─prusa-slicer
│ │ │ │ ├─prusa-slicer --gcodeviewer
/mnt/c/Users/bubni/Downloads/3DBenchy_0.15mm_PLA_MINI_2h0m.gcode
│ │ │ │ │ └─33*[{prusa-slicer}]
│ │ │ │ └─35*[{prusa-slicer}]
│ │ │ └─35*[{prusa-slicer}]
```
The PrusaSlicer-2.3 AppImage starts the prusa-slicer process packed inside
the AppImage, which in turn starts another AppImage instance, which then
starts another prusa-slicer or prusa-slicer --gcodeviewer.
It looks to me that the double fork is in action as the PrusaSlicer
AppImage runners are all children of the init process. I don't quite get
though, why the prusa-slicer instances (which are supposed to be binaries
inside the mounted squashfs filesystem) are displayed as if each
prusa-slicer started another instance. Maybe it is a bug in the pstree tool?
…On Thu, Sep 10, 2020 at 10:35 PM probonopd ***@***.***> wrote:
If those subprocesses are detached from their parent process (using a
double fork, for instance)
Is Prusa Slicer using double forks?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#419 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ABMPSI67JXWH2TM6ELOXYDTSFE2AJANCNFSM4DRMOEKQ>
.
|
Now it is getting interesting. The process tree in my previous post is real. The AppImage starter gets daemonized while the prusa-slicer application is execve'd on the initial process, therefore the prusa-slicer gets both the stdin/stdout/stderr and all the subprocesses retain the parent/child relationship. The AppImage squashfs server gets daemonized through a double fork, thus it is reparented to an init process. It cannot receive any SIGCHLD signals from the prusa-slicer when it dies, therefore the squashfs server has to do some form of polling. This polling is done in squashfuse/ll.c see setup_idle_timeout(), alarm_tick() and the comment
Maybe we are completely safe already and we don't know it? |
The timeout seems to be off in the AppImage runtime. It should be sufficient to add the timeout parameter to fusefs_main() to shut down by polling the number of open files. I still don't know how the AppImage daemon gets shut down currently, but that would likely have to be suppressed if using the polling. |
I am not sure how that works, but it seems to me that the AppImage daemon dies when the keepalive_pipe So it should be sufficient to disable the suicide of the AppImage daemon by the keepalive_pipe watching and to enable the idle timeout. Doing that, we are basically done with allowing our application to launch a child process and everything should work by counting the open file references by the squashfs part of the AppImage daemon. |
To support just a single instance of the AppImage daemon mounting just a single copy of the AppImage squashfs, I would encode a MD5 hash of a full canonical path of the AppImage file into the mount point name, thus there will be a single mount point generated for a single AppImage. Then the AppImage starter would first try to open the mount point. If that succeeds and the mount point is mounted, then the server must be running and once the application is started from the mount point, the AppImage server will know it from the number of open files it registers through the squashfs library. If the mount point does not exist yet, then the runner would fork the AppImage squashfs daemon. The only race condition I see is when the mount point directory exists, but the AppImage daemon is starting or dying. One may read /proc/self/mountinfo and search for the mount point. It says /tmp/.mount_PrusaSU65DAP ro,nosuid,nodev,relatime - fuse.PrusaSlicer-2.3.0-alpha0+867-linux-x64-g50a6680-202009102318.AppImage PrusaSlicer-2.3.0-alpha0+867-linux-x64-g50a6680-202009102318.AppImage ro,user_id=1000,group_id=1000 on my WSL2 system with When the runner opens the mount point and the mount point is mounted (confirmed by reading the /proc/self/mountinfo afterwards), then the other runner should not die because the file system access reference counter inside the squashfs daemon has already been incremented, so this is working correctly IMHO. When the runner opens the mount point and it is not mounted yet, then there is a chance that another runner is starting and it will mount the mount point in the meanwhile. If both try to mount, one of them will fail. This is burried deep in the squashfs library, thus it is more difficult to handle correctly. Not the least, there will be situations where the AppImage squashfs daemon dies unexpectedly for whatever reason (out of RAM, someone sends it a SIGKILL etc). Then the filesystem will unmount, but the mount point directory will not be deleted. |
Many executables use the name under which they are invoked (argv[0]) to select one of multiple different roles they can fulfill - like "xz" which, if invoked as "xzcat" or "unxz" performs different operations.
It would make a lot of sense for AppImage files to allow for this, such that multiple executables inside the AppImage can all be executed by just creating differently named soft-links to the AppImage file.
I use this method in my https://github.com/lvml/makeaoi tool to allow packing multiple executables into the same directory - so their dependencies can be shared. (Think of examples like packaging "ffmpeg" and "ffprobe" into the same directory. Or "kdenlive" and its "kdenlive_render" batch processing tool.)
It would be great if AppImage would support this by also using the name it was invoked with to use names other than "AppRun" to run (if a file or link of that name exists).
The text was updated successfully, but these errors were encountered: