Follow Us

  • erpgap-icon
  • erpgap-icon
  • erpgap-icon

Odoo Signal Handlers: The Undocumented Power Tool

Understanding Odoo POSIX Signals for Process Management

erpgap-img

Odoo supports a set of POSIX signals that enable administrators and developers to manage and interact with Odoo processes efficiently. These signals facilitate smooth server shutdowns, worker management, real-time diagnostics, and debugging capabilities. Warning: It is crucial to avoid testing these signals on a production server unless you have thoroughly tested them in a safe environment. Additionally, avoid using them on Odoo.sh, as it is a PaaS environment that may already employ some of these signals for internal operations.

Key POSIX Signals Used in Odoo

These are the POSIX signals that Odoo handles:

______________ ____________________________________________
SIGINT Force a graceful shutdown of the Odoo server
SIGTERM Force a shutdown of the Odoo server
SIGCLH Internal mechanism for process reaping
SIGHUP Restart the Odoo server
SIGXCPU Trigger when worker CPU time limit is exceeded
SIGQUIT Dump stack traces for debugging purposes
SIGUSR1 Log ORM (Object-Relational Mapping) statistics

By using these signals, administrators can perform essential tasks like restarting the server safely, running diagnostics, and addressing performance bottlenecks. Signal-based management is a vital part of ensuring the reliability, scalability, and maintainability of an Odoo instance.

Practical Examples of Using Signals in Odoo

Identifying Odoo Processes

Let's find the Odoo processes running on your server

ps -ef | grep odoo

You will see something similar to this:

dd@pro-dd:~$ ps -ef | grep odoo
dd         14753   11704  4 19:07 pts/0    00:00:02 python3 ~/git/odoo/18.0/odoo-bin --addons-path ~/git/oe/18.0,~/git/odoo/18.0/addons,~/git/odoo-themes/18.0 -d v18_demo1 --workers 5
dd         14777   14753  0 19:07 pts/0    00:00:00 python3 ~/git/odoo/18.0/odoo-bin --addons-path ~/git/oe/18.0,~/git/odoo/18.0/addons,~/git/odoo-themes/18.0 -d v18_demo1 --workers 5
dd         14778   14753  0 19:07 pts/0    00:00:00 python3 ~/git/odoo/18.0/odoo-bin --addons-path ~/git/oe/18.0,~/git/odoo/18.0/addons,~/git/odoo-themes/18.0 -d v18_demo1 --workers 5
dd         14779   14753  0 19:07 pts/0    00:00:00 python3 ~/git/odoo/18.0/odoo-bin --addons-path ~/git/oe/18.0,~/git/odoo/18.0/addons,~/git/odoo-themes/18.0 -d v18_demo1 --workers 5
dd         14780   14753  0 19:07 pts/0    00:00:00 python3 ~/git/odoo/18.0/odoo-bin --addons-path ~/git/oe/18.0,~/git/odoo/18.0/addons,~/git/odoo-themes/18.0 -d v18_demo1 --workers 5
dd         14782   14753  0 19:07 pts/0    00:00:00 python3 ~/git/odoo/18.0/odoo-bin --addons-path ~/git/oe/18.0,~/git/odoo/18.0/addons,~/git/odoo-themes/18.0 -d v18_demo1 --workers 5
dd         14783   14753  4 19:07 pts/0    00:00:02 ~/envs/18.0/bin/python3 ~/git/odoo/18.0/odoo-bin gevent --addons-path ~/git/oe/18.0,~/git/odoo/18.0/addons,~/git/odoo-themes/18.0 -d v18_demo1 --workers 5
dd         14785   14753  0 19:07 pts/0    00:00:00 python3 ~/git/odoo/18.0/odoo-bin --addons-path ~/git/oe/18.0,~/git/odoo/18.0/addons,~/git/odoo-themes/18.0 -d v18_demo1 --workers 5
dd         14788   14753  0 19:07 pts/0    00:00:00 python3 ~/git/odoo/18.0/odoo-bin --addons-path ~/git/oe/18.0,~/git/odoo/18.0/addons,~/git/odoo-themes/18.0 -d v18_demo1 --workers 5
dd         14829   11985  0 19:08 pts/1    00:00:00 grep --color=auto odoo

As we can see, we are running one main process (14753) plus 5 http workers (WorkerHTTP), plus a gevent worker for websockets and finally 2 cron workers (WorkerCron). When Odoo starts he also explains that for you:

(18.0) dd@pro-dd:~$ runodoo18e -d v18_demo1 --workers 5 
2024-12-15 19:07:20,778 14753 INFO ? odoo: Odoo version 18.0 
2024-12-15 19:07:20,778 14753 INFO ? odoo: addons paths: ['~/git/odoo/18.0/odoo/addons', '~/.local/share/Odoo/addons/18.0', '~/git/oe/18.0', '~/git/odoo/18.0/addons', '~/git/odoo-themes/18.0'] 
2024-12-15 19:07:20,778 14753 INFO ? odoo: database: default@default:default 
2024-12-15 19:07:20,832 14753 INFO ? odoo.addons.base.models.ir_actions_report: You need Wkhtmltopdf to print a pdf version of the reports. 
2024-12-15 19:07:20,832 14753 INFO ? odoo.addons.base.models.ir_actions_report: You need Wkhtmltoimage to generate images from html. 
2024-12-15 19:07:20,897 14753 INFO ? odoo.service.server: HTTP service (werkzeug) running on 0.0.0.0:8069 
2024-12-15 19:07:20,905 14753 INFO v18_demo1 odoo.modules.loading: loading 1 modules... 
2024-12-15 19:07:20,927 14753 INFO v18_demo1 odoo.modules.loading: 1 modules loaded in 0.02s, 0 queries (+0 extra) 
2024-12-15 19:07:20,938 14753 INFO v18_demo1 odoo.modules.loading: loading 57 modules... 
2024-12-15 19:07:21,150 14753 INFO v18_demo1 odoo.modules.loading: 57 modules loaded in 0.21s, 0 queries (+0 extra) 
2024-12-15 19:07:21,189 14753 INFO v18_demo1 odoo.modules.loading: Modules loaded. 
2024-12-15 19:07:21,191 14753 INFO v18_demo1 odoo.modules.registry: Registry loaded in 0.294s 
2024-12-15 19:07:21,191 14753 INFO v18_demo1 odoo.sql_db: ConnectionPool(read/write;used=0/count=0/max=64): Closed 1 connections  
2024-12-15 19:07:21,195 14777 INFO v18_demo1 odoo.service.server: Worker WorkerHTTP (14777) alive 
2024-12-15 19:07:21,197 14778 INFO v18_demo1 odoo.service.server: Worker WorkerHTTP (14778) alive 
2024-12-15 19:07:21,198 14779 INFO v18_demo1 odoo.service.server: Worker WorkerHTTP (14779) alive 
2024-12-15 19:07:21,199 14780 INFO v18_demo1 odoo.service.server: Worker WorkerHTTP (14780) alive 
2024-12-15 19:07:21,200 14782 INFO v18_demo1 odoo.service.server: Worker WorkerHTTP (14782) alive 
2024-12-15 19:07:21,202 14785 INFO v18_demo1 odoo.service.server: Worker WorkerCron (14785) alive 
2024-12-15 19:07:21,202 14788 INFO v18_demo1 odoo.service.server: Worker WorkerCron (14788) alive 
2024-12-15 19:07:21,541 14783 INFO ? odoo: Odoo version 18.0 
2024-12-15 19:07:21,541 14783 INFO ? odoo: addons paths: ['~/git/odoo/18.0/odoo/addons', '~/.local/share/Odoo/addons/18.0', '~/git/oe/18.0', '~/git/odoo/18.0/addons', '~/git/odoo-themes/18.0'] 
2024-12-15 19:07:21,541 14783 INFO ? odoo: database: default@default:default 
2024-12-15 19:07:21,596 14783 INFO ? odoo.addons.base.models.ir_actions_report: You need Wkhtmltopdf to print a pdf version of the reports. 
2024-12-15 19:07:21,596 14783 INFO ? odoo.addons.base.models.ir_actions_report: You need Wkhtmltoimage to generate images from html. 
2024-12-15 19:07:21,678 14783 INFO ? odoo.service.server: Evented Service (longpolling) running on 0.0.0.0:8072 

Restarting the Odoo Server (SIGHUP)

To restart the Odoo server, use the following command:

kill -HUP 14753

This sends the SIGHUP signal to the main process (PID 14753). As a result, Odoo stops all child processes and restarts them with new process IDs.

Example log output after issuing this command

2024-12-15 19:10:04,860 14753 INFO v18_demo1 odoo.service.server: Stopping gracefully 
2024-12-15 19:10:04,861 14779 INFO v18_demo1 odoo.service.server: Worker (14779) exiting. request_count: 0, registry count: 1. 
2024-12-15 19:10:04,861 14780 INFO v18_demo1 odoo.service.server: Worker (14780) exiting. request_count: 0, registry count: 1. 
2024-12-15 19:10:04,861 14777 INFO v18_demo1 odoo.service.server: Worker (14777) exiting. request_count: 0, registry count: 1. 
2024-12-15 19:10:04,861 14778 INFO v18_demo1 odoo.service.server: Worker (14778) exiting. request_count: 0, registry count: 1. 
2024-12-15 19:10:04,861 14782 INFO v18_demo1 odoo.service.server: Worker (14782) exiting. request_count: 0, registry count: 1. 
2024-12-15 19:10:04,911 14785 INFO v18_demo1 odoo.service.server: Worker (14785) exiting. request_count: 2, registry count: 1. 
2024-12-15 19:10:04,942 14788 INFO v18_demo1 odoo.service.server: Worker (14788) exiting. request_count: 2, registry count: 1. 
2024-12-15 19:10:05,634 14753 INFO ? odoo: Odoo version 18.0 
2024-12-15 19:10:05,634 14753 INFO ? odoo: addons paths: ['~/git/odoo/18.0/odoo/addons', '~/.local/share/Odoo/addons/18.0', '~/git/oe/18.0', '~/git/odoo/18.0/addons', '~/git/odoo-themes/18.0'] 
2024-12-15 19:10:05,634 14753 INFO ? odoo: database: default@default:default 
2024-12-15 19:10:05,686 14753 INFO ? odoo.addons.base.models.ir_actions_report: You need Wkhtmltopdf to print a pdf version of the reports. 
2024-12-15 19:10:05,686 14753 INFO ? odoo.addons.base.models.ir_actions_report: You need Wkhtmltoimage to generate images from html. 
2024-12-15 19:10:05,749 14753 INFO ? odoo.service.server: HTTP service (werkzeug) running on 0.0.0.0:8069 
2024-12-15 19:10:05,757 14753 INFO v18_demo1 odoo.modules.loading: loading 1 modules... 
2024-12-15 19:10:05,783 14753 INFO v18_demo1 odoo.modules.loading: 1 modules loaded in 0.03s, 0 queries (+0 extra) 
2024-12-15 19:10:05,800 14753 INFO v18_demo1 odoo.modules.loading: loading 57 modules... 
2024-12-15 19:10:06,020 14753 INFO v18_demo1 odoo.modules.loading: 57 modules loaded in 0.22s, 0 queries (+0 extra) 
2024-12-15 19:10:06,071 14753 INFO v18_demo1 odoo.modules.loading: Modules loaded. 
2024-12-15 19:10:06,074 14753 INFO v18_demo1 odoo.modules.registry: Registry loaded in 0.324s 
2024-12-15 19:10:06,074 14753 INFO v18_demo1 odoo.sql_db: ConnectionPool(read/write;used=0/count=0/max=64): Closed 1 connections  
2024-12-15 19:10:06,078 14873 INFO v18_demo1 odoo.service.server: Worker WorkerHTTP (14873) alive 
2024-12-15 19:10:06,079 14874 INFO v18_demo1 odoo.service.server: Worker WorkerHTTP (14874) alive 
2024-12-15 19:10:06,081 14876 INFO v18_demo1 odoo.service.server: Worker WorkerHTTP (14876) alive 
2024-12-15 19:10:06,081 14875 INFO v18_demo1 odoo.service.server: Worker WorkerHTTP (14875) alive 
2024-12-15 19:10:06,082 14878 INFO v18_demo1 odoo.service.server: Worker WorkerHTTP (14878) alive 
2024-12-15 19:10:06,084 14883 INFO v18_demo1 odoo.service.server: Worker WorkerCron (14883) alive 
2024-12-15 19:10:06,084 14881 INFO v18_demo1 odoo.service.server: Worker WorkerCron (14881) alive 
2024-12-15 19:10:06,438 14879 INFO ? odoo: Odoo version 18.0 
2024-12-15 19:10:06,438 14879 INFO ? odoo: addons paths: ['~/git/odoo/18.0/odoo/addons', '~/.local/share/Odoo/addons/18.0', '~/git/oe/18.0', '~/git/odoo/18.0/addons', '~/git/odoo-themes/18.0'] 
2024-12-15 19:10:06,438 14879 INFO ? odoo: database: default@default:default 

Force Worker CPU Time Limit (SIGXCPU)

If you want to simulate what happens when a worker exceeds its CPU time limit, you can send a SIGXCPU signal to a specific worker process:

We see process 14779 to 14788 being recycled into pid 14873 to 14881 but main process keeps the same pid 14753. Let's try forcing a CPU time limit to on WorkerHTTP and see what happens.

dd@pro-dd:~$ kill -XCPU 14878

The log output will display the following:

2024-12-15 19:14:47,149 14878 INFO v18_demo1 odoo.service.server: Worker (14878) CPU time limit (60) reached. 
2024-12-15 19:14:47,150 14878 ERROR v18_demo1 odoo.service.server: Worker (14878) Exception occurred, exiting... 
Traceback (most recent call last):
  File "/home/dd/git/odoo/18.0/odoo/service/server.py", line 1093, in run
    t.join()
  File "/usr/lib/python3.12/threading.py", line 1147, in join
    self._wait_for_tstate_lock()
  File "/usr/lib/python3.12/threading.py", line 1167, in _wait_for_tstate_lock
    if lock.acquire(block, timeout):
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dd/git/odoo/18.0/odoo/service/server.py", line 1023, in signal_time_expired_handler
    raise Exception('CPU time limit exceeded.')
Exception: CPU time limit exceeded.
2024-12-15 19:14:47,327 14976 INFO v18_demo1 odoo.service.server: Worker WorkerHTTP (14976) alive 

Meaning that we force the termination of 14878 and Odoo triggered the creation of pid 14976. Let's now get creating and force a timeout on the main pid and see what happens.

dd@pro-dd:~$ kill -XCPU 14753

Odoo will take care of killing the child processes and Odoo server will forcefully stop.

2024-12-15 19:19:27,633 14976 INFO ? odoo.service.server: Worker (14976) Parent changed 
2024-12-15 19:19:30,677 14874 INFO ? odoo.service.server: Worker (14874) Parent changed 
2024-12-15 19:19:30,677 14875 INFO ? odoo.service.server: Worker (14875) Parent changed 
2024-12-15 19:19:30,677 14873 INFO ? odoo.service.server: Worker (14873) Parent changed 
2024-12-15 19:19:30,696 14876 INFO ? odoo.service.server: Worker (14876) Parent changed 
2024-12-15 19:19:31,149 14879 WARNING ? odoo.service.server: Gevent Parent changed: 14879 
2024-12-15 19:19:31,639 14976 INFO v18_demo1 odoo.service.server: Worker (14976) exiting. request_count: 0, registry count: 1. 
2024-12-15 19:19:34,119 14883 INFO ? odoo.service.server: Worker (14883) Parent changed 
2024-12-15 19:19:34,683 14875 INFO v18_demo1 odoo.service.server: Worker (14875) exiting. request_count: 0, registry count: 1. 
2024-12-15 19:19:34,683 14873 INFO v18_demo1 odoo.service.server: Worker (14873) exiting. request_count: 0, registry count: 1. 
2024-12-15 19:19:34,683 14874 INFO v18_demo1 odoo.service.server: Worker (14874) exiting. request_count: 0, registry count: 1. 
2024-12-15 19:19:34,701 14876 INFO v18_demo1 odoo.service.server: Worker (14876) exiting. request_count: 0, registry count: 1. 

This is not the correct way to stop the server, the best way would just be a SIGNT as follows.

Shutting Down the Odoo Server (SIGINT)

To properly shut down the Odoo server, the best practice is to send the SIGINT signal to the main process:

kill -INT 14753

This ensures that all child processes (workers) are gracefully stopped before the main Odoo process is shut down. Unlike SIGKILL, which forces termination, SIGINT ensures no data corruption or incomplete processing.

Dumping the Stack Trace (SIGQUIT)

To diagnose deadlocks or identify which threads are waiting on locks, you can trigger a stack dump. This is done by sending a SIGQUIT signal to a worker process:

dd@pro-dd:~$ kill -QUIT 17172

Dumping the stack might point to multiple threads are all waiting for the same lock, it might be a sign of a deadlock.The log will display the stack trace, which may reveal information like:

2024-12-15 20:27:20,902 17172 INFO v18_demo1 odoo.tools.misc: 
# Thread: <Thread(Worker WorkerHTTP (17172) workthread, started daemon 130846535190208)> (db:v18_demo1) (uid:2) (url:http://odoo/mail/thread/data) (qc:n/a qt:0.028 pt:13.732)
File: "/usr/lib/python3.12/threading.py", line 1030, in _bootstrap
  self._bootstrap_inner()
File: "/usr/lib/python3.12/threading.py", line 1073, in _bootstrap_inner
  self.run()
File: "/usr/lib/python3.12/threading.py", line 1010, in run
  self._target(*self._args, **self._kwargs)
File: "/home/dd/git/odoo/18.0/odoo/service/server.py", line 1112, in _runloop
  self.sleep()
File: "/home/dd/git/odoo/18.0/odoo/service/server.py", line 1027, in sleep
  select.select([self.multi.socket, self.wakeup_fd_r], [], [], self.multi.beat)

# Thread: <_MainThread(MainThread, started 130847655657600)> (db:v18_demo1) (uid:n/a) (url:n/a) (qc:n/a qt:n/a pt:n/a)
File: "/home/dd/git/odoo/18.0/odoo-bin", line 8, in <module>
  odoo.cli.main()
File: "/home/dd/git/odoo/18.0/odoo/cli/command.py", line 66, in main
  o.run(args)
File: "/home/dd/git/odoo/18.0/odoo/cli/server.py", line 180, in run
  main(args)
File: "/home/dd/git/odoo/18.0/odoo/cli/server.py", line 173, in main
  rc = odoo.service.server.start(preload=preload, stop=stop)
File: "/home/dd/git/odoo/18.0/odoo/service/server.py", line 1402, in start
  rc = server.run(preload, stop)
File: "/home/dd/git/odoo/18.0/odoo/service/server.py", line 978, in run
  self.process_spawn()
File: "/home/dd/git/odoo/18.0/odoo/service/server.py", line 884, in process_spawn
  self.worker_spawn(WorkerHTTP, self.workers_http)
File: "/home/dd/git/odoo/18.0/odoo/service/server.py", line 801, in worker_spawn
  worker.run()
File: "/home/dd/git/odoo/18.0/odoo/service/server.py", line 1093, in run
  t.join()
File: "/usr/lib/python3.12/threading.py", line 1147, in join
  self._wait_for_tstate_lock()
File: "/usr/lib/python3.12/threading.py", line 1167, in _wait_for_tstate_lock
  if lock.acquire(block, timeout):
File: "/home/dd/git/odoo/18.0/odoo/tools/misc.py", line 918, in dumpstacks
  for line in extract_stack(stack): 

Taking a closer look into this beginning line:

 ... (db:v18_demo1) (uid:2) (url:http://odoo/mail/thread/data) (qc:n/a qt:0.028 pt:13.732)

Where:

  • qc = Query Count
  • qt = Query Time
  • pt = Python Time

Summary of Best Practices

  1. Never test signals on a production server without testing them in a development or QA environment first.
  2. Avoid testing signals on Odoo.sh, as it already uses signals for internal operations.
  3. Use SIGINT for clean server shutdowns.
  4. Use SIGHUP to restart the server and reload configurations.
  5. Use SIGQUIT to generate stack dumps for debugging.
  6. Use SIGXCPU to simulate worker CPU time limit events and observe the system’s behavior.

Conclusion

POSIX signals provide a powerful way to manage and debug an Odoo server. They enable real-time process control, from restarting the server to debugging performance issues. Proper use of these signals enhances the maintainability and reliability of your Odoo environment. Remember to exercise caution when using signals on production servers and always test them in a controlled environment beforehand.

If you want to know more about Odoo, Contact us

Follow Us on Social Media

Stay connected with ERPGAP and follow us on this journey. You can view updates on LinkedIn and Twitter.

Taking your business to the next level

BOOK A FREE 30-min CONSULTATION erpgap-icon

erpgap-icon Official Odoo Partner

Diogo Duarte
4 min read December 17, 2024

Subscribe To Our Newsletter