Search code examples
pythonlinuxloopsexitsystemd

Stop systemd service running a python program


I created a service to run a Python program and added some lines of code to create a lock to avoid launching it twice.

Unfortunately I don't know how to configure the service to stop the running program correctly. When running the stop command it doesn't delete the lock, then I can't start the service anymore. If I execute the program myself via CLI and exit with a Ctrl+C, the lock is deleted.

I've read the manual about KillMode, ExecStop and Signal. My understanding is that the default configuration was the one I needed.

Any help please?

Main program

if __name__ == '__main__':

    #Creating lock to avoid launching program twice
    lock = pathlib.Path("program.lock")

    if not lock.exists():

        lock_acquired_on = datetime.now()

        with open('program.lock', 'w') as lock:
            lock.write(f'Lock acquired on {lock_acquired_on}')
            logger.info('Added lock file to avoid running the program twice.')
    
        try:
            while True:

                #Doing stuff here


        except KeyboardInterrupt:
            close_program() #Close other threads

        #Removing the lock file
        os.remove(pathlib.Path("program.lock"))

    else:
        with open('program.lock', 'r') as lock:
            lock_acquisition_time = str(lock.readlines()[0])
            logger.info('Programme Maquette Status is already running.') 
            logger.info(lock_acquisition_time)

Service

[Unit]
Description=Programme Maquette IoT End-to-End
After=multi-user.target
Conflicts=getty@tty1.service

[Service]
WorkingDirectory=/home/pi/Documents/ProductionMaquette
Type=simple
ExecStart=/usr/local/bin/python3.8 /home/pi/Documents/ProductionMaquette/Lo_main.py
StandardInput=tty-force

[Install]
WantedBy=multi-user.target

Solution

  • Systemd sends the SIGTERM to the process - so you need to handle that.

    So following little example uses the a signal handler for SIGTERMto clean up a file. Actually it uses atexit to clean up the file, as that handles standard exit conditions as well and a signal handler to initiate in "normal" closing down of the process on receiving the SIGTERM signal

    import atexit
    import signal
    import os
    
    locking_file = "/var/lock/my_service.lock"
    
    if __name__ == '__main__':
    
        def clean_lock():
            # atexit handler to clean up a file
            os.remove(locking_file)
    
        def signal_term_handler(sigNum, frame):
            # on receiving a signal initiate a normal exit
            raise SystemExit('terminating')
    
        with open("test_file.lock", "w") as lock:
             while True:
                 lock.write("x")
                 time.sleep(10)
    
        # register the cleanup handler
        atexit.register(clean_lock)
        # register the signal handler
        signal.signal(signal.SIGTERM, signal_term_handler)
    

    As a note: there is a file locking library you might want to look at:https://pypi.org/project/filelock/ as that should handle that use case as well.

    It is not only testing for presents of a file but uses the os-file locking mechanism. I.e. not only the existence of the file is tested - but if it can be locked as well. In effect that means even if the file still exists but the previous process died it is not a problem, as the file is no longer locked.