'Inherit class Worker on Odoo15

In one of my Odoo installation I need to setup the socket_timeout variable of WorkerHTTP class directly from Python code, bypassing the usage of environment variable ODOO_HTTP_SOCKET_TIMEOUT.

If you never read about it, you can check here for more info: https://github.com/odoo/odoo/commit/49e3fd102f11408df00f2c3f6360f52143911d74#diff-b4207a4658979fdb11f2f2fa0277f483b4e81ba59ed67a5e84ee260d5837ef6d

In Odoo15, which i'm using, Worker classes are located at odoo/service/server.py

My idea was to inherit constructor for Worker class and simply setup self.sock_timeout = 10 or another value, but I can't make it work with inheritance.

EDIT: I almost managed it to work, but I have problems with static methods.

STEP 1:

Inherit WorkerHTTP constructor and add self.socket_timeout = 10

Then, I also have to inherit PreforkServer and override process_spawn() method so I can pass WorkerHttpExtend instead of WorkerHTTP, as argument for worker_spawn() method.

class WorkerHttpExtend(WorkerHTTP):
    """ Setup sock_timeout class variable when WorkerHTTP object gets initialized"""
    def __init__(self, multi):
        super(WorkerHttpExtend, self).__init__(multi)
        self.sock_timeout = 10
        logging.info(f'SOCKET TIMEOUT: {self.sock_timeout}')


class PreforkServerExtend(PreforkServer):
    """ I have to inherit PreforkServer and override process_spawn() 
    method so I can pass WorkerHttpExtend
    instead of WorkerHTTP, as argument for worker_spawn() method.
    """

    def process_spawn(self):
        if config['http_enable']:
            while len(self.workers_http) < self.population:
                self.worker_spawn(WorkerHttpExtend, self.workers_http)
            if not self.long_polling_pid:
                self.long_polling_spawn()
        while len(self.workers_cron) < config['max_cron_threads']:
            self.worker_spawn(WorkerCron, self.workers_cron)

STEP 2:

static method start() should initialize PreforkServer with PreforkServerExtend, not with PreforkServer (last line in the code below). This is where I start to have problems.

def start(preload=None, stop=False):
   """Start the odoo http server and cron processor."""

  global server
    load_server_wide_modules()
    if odoo.evented:
        server = GeventServer(odoo.service.wsgi_server.application)
    elif config['workers']:
        if config['test_enable'] or config['test_file']:
            _logger.warning("Unit testing in workers mode could fail; use --workers 0.")
        server = PreforkServer(odoo.service.wsgi_server.application)

STEP 3: At this point if I wanna go further (which I did) I should copy the whole start() method and import all package I need to make it work

import odoo
from odoo.service.server import WorkerHTTP, WorkerCron, PreforkServer, load_server_wide_modules, \
    GeventServer, _logger, ThreadedServer, inotify, FSWatcherInotify, watchdog, FSWatcherWatchdog, _reexec
from odoo.tools import config

I did it and then in my custom start() method I wrote line

server = PreforkServerExtend(odoo.service.wsgi_server.application)

but even then, how do I tell to execute my start() method, instead of the original one??

I'm sure this would eventually work (mabe not safely, but would work) because at some point I wasn't 100% sure what I was doing, so I put my inherit classes WorkerHttpExtend and PreforkServerExtend in the original odoo/service/server.py and initialized server obj with PreforkServerExtend instead of PreforkServer.

server = PreforkServer(odoo.service.wsgi_server.application)

It works then: I get custom socket timeout value, print and logging info when Odoo service start, because PreforkServerExtend will call custom class on cascade at that point, otherwise my inherited class are there but they will never be called.

So I guess if I could tell the system to run my start() method I would have done it.

STEP 4 (not reached yet):

I'm pretty sure that start() method is called in odoo/cli/server.py, in main() method:

rc = odoo.service.server.start(preload=preload, stop=stop)

I could go deeper but I don't think the effort is worth for what I need. So technically if I would be able to tell the system which start() method to choose, I would have done it. Still not sure it is safe procedure (probably not much actually, but at this point I was just experimenting), but I wonder if there is an easier method to set up socket timeout without using environment variable ODOO_HTTP_SOCKET_TIMEOUT.

I'm pretty sure there is an easier method than i'm doing, with low level python or maybe even with a class in odoo/service/server, but I can't figure out for now. If some one has an idea, let me know!



Solution 1:[1]

Working solution: Introduced to Monkeypatch in this post

Possible for a class to look down at subclass constructor?

solved by patching process_request attribute of class WorkerHTTP

import errno
import fcntl
import socket
import odoo
import odoo.service.server as srv

class WorkerHttpProcessRequestPatch(srv.WorkerHTTP):

    def process_request(self, client, addr):
        client.setblocking(1)
        # client.settimeout(self.sock_timeout)
        client.settimeout(10) # patching timeout setup to a needed value
        client.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)

        flags = fcntl.fcntl(client, fcntl.F_GETFD) | fcntl.FD_CLOEXEC
        fcntl.fcntl(client, fcntl.F_SETFD, flags)
        self.server.socket = client
        try:
            self.server.process_request(client, addr)
        except IOError as e:
            if e.errno != errno.EPIPE:
                raise
        self.request_count += 1

# Switch process_request class attribute - this is what I needed to make it work
odoo.service.server.WorkerHTTP.process_request = WorkerHttpProcessRequestPatch.process_request

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1