Trapping the succesful close of a torizon docker container

Hi,

I will simplify my question.

My application running in a Torizon container is automatically called up with a docker-compose.yml

When the user is finished, they shut down the application and the container shuts.

How do I call up “sudo shutdown now -h” so it gracefully shuts down?

I was going to trap the container exit, looking for SIGTERM signal. But that doesnt seem to work.

OK. More details showing what I tried below the dotted line.

Power to the people!

FatLinux


I have a STORYBOARD GUI application running in a torizon container. The backend to this is a C++ application. When the container closes (it is PID 1 because I enter with tini) I kill the application within the container with the c++ command:-

kill(1, SIGUSR1); // tini means that the parent is always pid 1

When executed in the application inside the container, my STORYBOARD application closes down

RD_1 | 1676937954 void SystemManager::shutdown(uint8_t): Back up completed
RDl_1 | 1676937954 void SystemManager::shutdown(uint8_t): Killing the parent PID 1
RD_1 | [INFO tini (1)] Spawned child process ‘/opt/coaguscan.sh’ with pid ‘7’
RD_1 | [INFO tini (1)] Main child exited with signal (with signal ‘User defined signal 1’)
torizon_lol_1 exited with code 138

I get Error code 138 because of the SIGUSR1 used to kill the application. If you change the C++ snippet for SIGTERM, error goes to code 0.

So I want to trap when the container is closed, so I can run a few commands and close down. Nothing I have tried seems to work.

So to test, when in the Torizon host I set up test traps like this.

colibri-imx8x-07021201:~$ trap – “{ echo " TERM”;}" SIGTERM
colibri-imx8x-07021201:~$ trap – “{ echo " USR1”;}" SIGUSR1
colibri-imx8x-07021201:~$ trap – “{ echo " INT”;}" SIGINT

You can test this by pressing ctrl-c and the word “^c INT” appears

However, when you run the container and exit it gracefully with ^c. It does not echo back “INT” to me at the end

Similarly when I run the snippet of C++

kill(1, SIGUSR1).

The docker container shuts down but we don’t get the word “USR1”. Somehow the signals from the application running inside the container do not make it back outside the container.

Why is this? What is happening to the signals back to the Torizon host?

Best regards

Fat Linux

Greetings @FatLinux,

Just to make sure I understand your goal. Do you need to be able to trap signals happening inside a container outside of the container on the host? Or do you just need the system to shutdown when the container stops/exits?

Best Regards,
Jeremias

Hi Jeremias,

I just need Linux to shut down properly when the container stops/exits.

My container closes properly, and everything is left in the right state to boot up again nicely.

My C++ backend has the statement kill(1, SIGTERM);

lol_1 | ACTION [11.744735]:ACTION: Invoke [close_database_response]->[gra.lua] on app [app]
lol_1 | ACTION [11.744771]:Lua: [Application:HandleCloseDatabaseResponse]
lol_1 | INFO [11.744925]:** Backup data to SD card file
lol_1 | 1677063294 void SystemManager::shutdown(uint8_t): Back up the database
lol_1 | EVENT [11.961996]:SBIO received event [usb_storage_connected] 17 bytes
lol_1 | EVENT [11.962107]:IO: Queue [1] usb_storage_connected
lol_1 | EVENT [11.962122]:IO: Dispatch [usb_storage_connected]
lol_1 | ACTION [11.962187]:ACTION: Invoke [usb_storage_connected]->[gra.lua] on app [app]
lol_1 | ACTION [11.962223]:Lua: [USBExport:HandleUSBConnected]
lol_1 | the path is /media/7A9C-A971
lol_1 | 1677063295 void SystemManager::shutdown(uint8_t): Back up completed
lol_1 | 1677063295 void SystemManager::shutdown(uint8_t): Killing the parent PI D 1
lol_1 | 1677063295 int Temperature_control_interface::turnOff() - called withou t initializing. Not fatal.
lol_1 | 1677063295 Power_switching::~Power_switching(): powerbank gpio chip clo sed
lol_1 | [INFO tini (1)] Spawned child process ‘/opt/coaguscan.sh’ with pid ‘6’
lol_1 | [INFO tini (1)] Main child exited normally (with status ‘0’)
torizon_lol_1 exited with code 0

I was hoping to trap this event and call something like “shutdown -h now” which should put everything away properly.

If I don’t do a graceful shutdown, I get the occasional boot up issue (i.e. system wont start) up, until the battery is removed and replaced again.

We had this before, it just cuts power

std::string command = "dbus-send --system --print-reply --dest=org.freedesktop.login1 "
"/org/freedesktop/login1 "
“"org.freedesktop.login1.Manager.PowerOff" boolean:true”;

system(command.c_str()

This does work and kill the current job; but it doesnt run all the class destructors and switch all my hardware off and backup my files.

I look forward to hearing your views.

Cheers

Richard (Fat Linux)

Hello @FatLinux ,
Maybe these links can help you:

Best regards,
Josep

Hi Josep,

So options:

  1. Bind the container /bin and host /bin to access systemd command, maybe /sbin and host /sbin to get the shutdown command.

  2. Bind two volumes together, and put a loop that is waiting for inotifywait to notice changes, when it does run a showdown -h now from the out side. When you want to shut down put a dummy file in the bound volumes.

  3. Add a line to the script that calls the ENTRYPOINT script which runs the shutdown -now.

  4. Add a CMD to the Entrypoint script to add shutdwon on the end.

I have tried these already apart from #2, but I will revise what I did and try again.

Cheers

Richard

Just to give an idea in the same vein as idea #2. We actually do a similar process to automatically reboot the device when an OTA update is performed in TorizonCore. The update client application creates a “sentinel” file when a reboot should occur. This sentinel file is tracked by systemd via ostree-pending-reboot.path. This then triggers the systemd service ostree-pending-reboot.service, which is what actually calls the reboot command.

I imagine you could do something similar with your setup. Maybe during your application closing process it can create this “sentinel” file which can trigger a reboot or even a script that makes sure everything is closed out before it reboots.

Best Regards,
Jeremias

Hi Jeremias,

#2 is the really easy and graceful way to close down your system from inside a Torizon container running a Storyboard GUI application.

For everybody else’s benefit when they found themselves in the hope of finding help to do this…

Make a wee script to shut everything down, called goodNight.sh in your home directory.

$ nano goodNight.sh
#!/bin/bash
# (c) Richard Day
# stop the container with a GUI in it, then shut down Linux ready to start again

# Check if file exists
if test -f /tmp/goodNight; then
        # delete the sentinel file straight away otherwise we get stuck in a loop
        rm /tmp/goodNight
        echo "Shutting down Storyboard GUI application"

        cd /home/torizon/
        docker-compose down

        echo "Shutting down Torizon gracefully "
        # save off anything else we want to keep
        # send turn off to the bluetooth devices
        poweroff
else
  echo "Starting ostree-goodNight services"
fi

Copy this over to the right directory

sudo cp goodNight.sh /usr/local/bin/goodNight.sh

Create a Service file to run that script when requested

sudo systemctl edit ostree-goodNight.service --full --force

This will bring up an editor for you to insert the following definitions:


[Unit]
Description="Calls the graceful shutdown goodNight"

[Service]
ExecStart=/usr/local/bin/goodNight.sh

[Install]
WantedBy=multi-user.target

Make that service start up and go

sudo systemctl start ostree-goodNight.service
sudo systemctl enable ostree-goodNight.service

Make a Path unit file to watch for a file to be changed

In your home directory make another file, ostree-goodNight.path

nano ostree-goodNight.path


[Unit]
Description="Monitor the /tmp/goodNight file for changes to its datestamp"

[Path]
PathExists=/tmp/goodNight
Unit=ostree-goodNight.service

[Install]
WantedBy=multi-user.target

Copy this over to the right directory

$ sudo cp ostree-goodNight.path /etc/systemd/system/ostree-goodNight.path

Make that Path Unit service start up and go

$ sudo systemctl start ostree-goodNight.path
$ sudo systemctl enable ostree-goodNight.path

Finally restart the systemctl daemon , see what these are doing…

$ sudo systemctl daemon-reload
$ systemctl status ostree-goodNight.path
● ostree-goodNight.path - "Monitor the /tmp/goodNight file for changes to its datestamp"
     Loaded: loaded (/etc/systemd/system/ostree-goodNight.path; enabled; vendor preset: disabled)
     Active: active (waiting) since Sun 2023-02-26 16:58:50 UTC; 10min ago
   Triggers: ● ostree-goodNight.service

Feb 26 16:58:50 colibri-imx8x-07021201 systemd[1]: Started "Monitor the /tmp/goodNight file for changes to its datestamp".
 status ostree-goodNight.path

$ systemctl status ostree-goodNight.service
● ostree-goodNight.service - "Calls the gracefull shutdown ostree-goodNight"
     Loaded: loaded (/etc/systemd/system/ostree-goodNight.service; enabled; vendor preset: disabled)
     Active: inactive (dead) since Sun 2023-02-26 16:58:51 UTC; 11min ago
TriggeredBy: ● ostree-goodNight.path
    Process: 667 ExecStart=/usr/local/bin/goodNight.sh (code=exited, status=0/SUCCESS)
   Main PID: 667 (code=exited, status=0/SUCCESS)

Feb 26 16:58:51 colibri-imx8x-07021201 systemd[1]: Started "Calls the gracefull shutdown ostree-goodNight".
Feb 26 16:58:51 colibri-imx8x-07021201 goodNight.sh[667]: Starting ostree-goodNight services
Feb 26 16:58:51 colibri-imx8x-07021201 systemd[1]: ostree-goodNight.service: Succeeded.

Now test it by writing a suitable file with the touch command

$ touch /tmp/goodNight

The module closes itself and powers down (note you need another posting -link- to add the gpio-powerdown device tree overlay to your device tree make a GPIO pin low that can feed into your power switch on the board, if you have this already the board will be off too)

So I would recommend just commenting out the “shutdown” command in the goodNight.sh shell script until you have finished your testing. I got into an infinite system up and shutdown loop the first time I ran my prototype version.

In your storyboard LUA code you need something like this…

function Application:goodNight()
	local symboLink = "touch /tmp/goodNight"		
	local popHandle = io.popen(symboLink)
 end

When you call this lua function it writes a file which triggers off the SYSTEMD.path event and that will call up the goodNight.sh file, which has the docker-compose down, and poweroff now commands in it. Everything will be closed down just right and ready for next time.


> application_1  | [INFO  tini (1)] Spawned child process '/opt/entryscript.sh' with pid '7'
> application_1  | [INFO  tini (1)] Main child exited normally (with status '0')
> torizon_application_1 exited with code 0

One final thing, make sure that in the docker-compose.yml file there is a section that binds the /tmp directory inside the container to the host /tmp directory.

  volumes:
      - type: bind
        source: /tmp
        target: /tmp

You are very welcome…

Richard
(ps. I am looking for a job)

2 Likes

Hello @FatLinux ,

Thank you very much for your feedback and the detailed explanation of your solution :slight_smile:

Have a look at our open positions :wink:

Best regards,
Josep