[Gambas-user] donot permit another instance of my program

Tobias Boege taboege at gmail.com
Thu May 31 18:13:38 CEST 2018


On Thu, 31 May 2018, Rolf-Werner Eilert wrote:
> Piccoro, you are right, a complete solution would then
> 
> - check for dbus, and if it isn't there:
> - check for pgrep, and if it isn't there:
> - use a lock file
> 

I don't know anything about dbus. In that case I shouldn't say this, but:
it looks like overhead for this task, even if it's just conceptual overhead.
I'm elaborating on the latter two fallbacks a bit here.

pgrep not being installed isn't a huge problem. If you wanted, you
could rewrite it in a few lines of Gambas -- if you assume your target
is a Linux system. My problem with pgrep is that you have to essentially
hard-code the name of your program into its code. Using Application.Name
is slightly better from a maintenance point of view, but still prevents
the user from renaming your .gambas executable or executing it using
alternate names or symlinks. If you want to go into /proc/<PID>/cmdline,
to find out what Gambas project really runs, you'll quickly find yourself
resolving symlinks and comparing checksums, for instance, and what about
two different versions of your project which exist on the same machine?
It just isn't a robust solution in the face of things that a user could
realistically want to do.

I like a lock file much more. Not only is it available on any POSIX
system, but you don't actually have to worry about application crashes
(not even the ones Application_Error can't save you from). Gambas'
LOCK and UNLOCK instructions use the lockf() interface, which provides
an exclusive lock managed by the kernel. If your process dies, the lock
will be automatically released from higher up, without you doing anything.

Play around with the attached project, start it as two processes A and B
and in particular convince yourself that:

  * if A is running and has the lock, B will fail to acquire the lock
    after trying for 2 seconds,
  * if A terminates, it will release the lock and if B is started
    afterwards, it will succeed acquiring the lock,
  * if A is running and has the lock, start B and within 2 seconds
    hit Ctrl+C in the terminal running A. The installed signal handler
    will make process A crash hard with a segfault (which is not
    recoverable via Application_Error). Gambas has no chance to release
    the lock itself, but instead the kernel releases the lock while
    tidying up process A, and B succeeds to acquire the lock as soon
    as that has happened!

I would advise that you read the manpage for lockf() though. It mentions
that the lock is released as soon as *any* file descriptor of a process
holding the lock is closed. This means that you *could* lock the file
twice (this is allowed by lockf) and lose your lock when the first stream
object representing your lock in Gambas goes out of scope -- even though
your process is still running. I don't think that will usually happen,
but still good to know. Also note that locks aren't inherited by child
processes, e.g. when you Shell, Exec or Task. Sadly, LOCK and UNLOCK
relying on lockf() are an implementation detail, so this kind of stuff
is missing from the documentation.

Finally, some standalone daemons like fetchmail use a combined approach:
when the daemon is starting, it writes its PID into a special file like
$HOME/.fetchmail.pid, possibly also putting a lock on that file.
Writing the PID directly into a predetermined file avoids all the
complications of using pgrep. Furthermore, a new instance of your program
can read the PID file, determine if the process is still alive by probing
the lock and then send commands or a "wake up" signal to the daemon.
For example, if I run `fetchmail` and it detects that fetchmail already
runs in daemon mode (by examining $HOME/.fetchmail.pid), the newer
fetchmail will not just terminate, it will tell the fetchmail daemon
that I want it to fetch my mail *now* and not on the next regular cycle.

You can also use that as a limited crash detection. If you write your
application so that it removes the lock file upon normal exit, and you
at some point detect that the lock file exists without being locked,
your program has likely crashed. Vim, for example, informs you about
this kind of stuff and offers to attempt to recover files, abort starting
or tidy up for you.

My point is: if you don't need fancy DBUS-IPC capabilities, reconsider
if a lock file should really be the last fallback on your list, and
not the default implementation.

Regards,
Tobi

-- 
"There's an old saying: Don't change anything... ever!" -- Mr. Monk
-------------- next part --------------
A non-text attachment was scrubbed...
Name: locktest-0.0.1.tar.gz
Type: application/gzip
Size: 11342 bytes
Desc: not available
URL: <http://lists.gambas-basic.org/pipermail/user/attachments/20180531/2a147abd/attachment-0001.gz>


More information about the User mailing list