Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
PCatinean committed Sep 9, 2023
1 parent bcd89a6 commit 2e5beb8
Show file tree
Hide file tree
Showing 3 changed files with 219 additions and 0 deletions.
23 changes: 23 additions & 0 deletions odoosh_queue_job/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#### Functionality

This module allows safe usage of OCA's queue_job on odoo.sh with multiple http workers.

In short: It disables the jobrunner on cron workers and uses a leader election system on the http workers so only one jobrunner is active at any given time.

#### Usage

1. Add this repository as a submodule in your odoo.sh instance

2. Include the module in the server_wide_modules config parameter of your odoo.conf file

```
[options]
server_wide_modules=web,odoosh_queue_job
```

3. Restart odoo

```
odoosh-restart http
```

184 changes: 184 additions & 0 deletions odoosh_queue_job/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import logging
import uuid
import time
import psycopg2

from contextlib import closing
from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT

from odoo.tools import config

_logger = logging.getLogger(__name__)

LEADER_CHECK_DELAY = 10


def main():
try:
from odoo.addons.queue_job import jobrunner
from odoo.addons.queue_job.jobrunner.channels import NOT_DONE
except Exception as ex:
_logger.error("Could not initialize - %s", ex)
return

Check warning on line 22 in odoosh_queue_job/__init__.py

View check run for this annotation

Codecov / codecov/patch

odoosh_queue_job/__init__.py#L20-L22

Added lines #L20 - L22 were not covered by tests

# Keeping the old reference to the original function
original__start_runner_thread = jobrunner._start_runner_thread
original__connection_info_for = jobrunner.runner._connection_info_for
ERROR_RECOVERY_DELAY = jobrunner.runner.ERROR_RECOVERY_DELAY
Database = jobrunner.runner.Database

def _start_runner_thread(self):
"""
Prevent jobrunner from initializing on odoo.sh cron workers
"""

# Odoo.sh cron workers always have limit_time_real_cron and
# limit_time_real_cron set to 0 so we use this to identify them
if config["limit_time_real_cron"] == 0 and config["limit_time_real"] == 0:
_logger.info("Odoo.sh cron worker detected, stopping jobrunner")
return
original__start_runner_thread(self)

Check warning on line 40 in odoosh_queue_job/__init__.py

View check run for this annotation

Codecov / codecov/patch

odoosh_queue_job/__init__.py#L38-L40

Added lines #L38 - L40 were not covered by tests

def _connection_info_for(db_name, uuid=False):
"""Inherit method to add the application_name to the connection info"""
connection_info = original__connection_info_for(db_name)

Check warning on line 44 in odoosh_queue_job/__init__.py

View check run for this annotation

Codecov / codecov/patch

odoosh_queue_job/__init__.py#L44

Added line #L44 was not covered by tests
if uuid:
connection_info["application_name"] = "jobrunner_%s" % uuid
return connection_info

Check warning on line 47 in odoosh_queue_job/__init__.py

View check run for this annotation

Codecov / codecov/patch

odoosh_queue_job/__init__.py#L46-L47

Added lines #L46 - L47 were not covered by tests

# DATABASE Class methods modified
def _init__(self, db_name):
"""Overriding Database __init__ to add a uuid to the connection info"""

self.db_name = db_name

Check warning on line 53 in odoosh_queue_job/__init__.py

View check run for this annotation

Codecov / codecov/patch

odoosh_queue_job/__init__.py#L53

Added line #L53 was not covered by tests
# Upstream code
# connection_info = _connection_info_for(db_name)

# Pledra customization starts here
self.uuid = str(uuid.uuid4())
connection_info = _connection_info_for(db_name, self.uuid)

Check warning on line 59 in odoosh_queue_job/__init__.py

View check run for this annotation

Codecov / codecov/patch

odoosh_queue_job/__init__.py#L58-L59

Added lines #L58 - L59 were not covered by tests
# Pledra customization ends here
self.conn = psycopg2.connect(**connection_info)
_logger.info("jobrunner initialized with uuid %s", self.uuid)
self.conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT)
self.has_queue_job = self._has_queue_job()

Check warning on line 64 in odoosh_queue_job/__init__.py

View check run for this annotation

Codecov / codecov/patch

odoosh_queue_job/__init__.py#L61-L64

Added lines #L61 - L64 were not covered by tests
# Upstream code
# if self.has_queue_job:
# self._initialize()

def check_leader(self):
"""Method to check if the current jobrunner is the leader"""
with closing(self.conn.cursor()) as cr:
cr.execute(

Check warning on line 72 in odoosh_queue_job/__init__.py

View check run for this annotation

Codecov / codecov/patch

odoosh_queue_job/__init__.py#L72

Added line #L72 was not covered by tests
"""
SELECT substring(application_name FROM 'jobrunner_(.*)')
FROM pg_stat_activity
WHERE application_name LIKE 'jobrunner_%'
ORDER BY backend_start
LIMIT 1;
"""
)
leader_uuid = cr.fetchone()[0]

Check warning on line 81 in odoosh_queue_job/__init__.py

View check run for this annotation

Codecov / codecov/patch

odoosh_queue_job/__init__.py#L81

Added line #L81 was not covered by tests
if leader_uuid != self.uuid:
_logger.info(

Check warning on line 83 in odoosh_queue_job/__init__.py

View check run for this annotation

Codecov / codecov/patch

odoosh_queue_job/__init__.py#L83

Added line #L83 was not covered by tests
"jobrunner %s: not leader of %s. leader: %s. sleeping %s sec.",
self.uuid,
self.db_name,
leader_uuid,
LEADER_CHECK_DELAY,
)

return False
_logger.info(

Check warning on line 92 in odoosh_queue_job/__init__.py

View check run for this annotation

Codecov / codecov/patch

odoosh_queue_job/__init__.py#L91-L92

Added lines #L91 - L92 were not covered by tests
"jobrunner %s is the leader of db %s",
self.uuid,
self.db_name,
)
return True
return False

Check warning on line 98 in odoosh_queue_job/__init__.py

View check run for this annotation

Codecov / codecov/patch

odoosh_queue_job/__init__.py#L97-L98

Added lines #L97 - L98 were not covered by tests

# QueueJobRunner class methods modified
def setup_databases(self):
"""Method split from the initialize_database for already created jobs"""
for db in self.db_by_name.values():
if not db.has_queue_job:
continue
db._initialize()

Check warning on line 106 in odoosh_queue_job/__init__.py

View check run for this annotation

Codecov / codecov/patch

odoosh_queue_job/__init__.py#L105-L106

Added lines #L105 - L106 were not covered by tests
with db.select_jobs("state in %s", (NOT_DONE,)) as cr:
for job_data in cr:
self.channel_manager.notify(db.db_name, *job_data)
_logger.info("queue job runner ready for db %s", db.db_name)

Check warning on line 110 in odoosh_queue_job/__init__.py

View check run for this annotation

Codecov / codecov/patch

odoosh_queue_job/__init__.py#L109-L110

Added lines #L109 - L110 were not covered by tests

def initialize_databases(self):
for db_name in self.get_db_names():
db = Database(db_name)

Check warning on line 114 in odoosh_queue_job/__init__.py

View check run for this annotation

Codecov / codecov/patch

odoosh_queue_job/__init__.py#L114

Added line #L114 was not covered by tests
if db.has_queue_job:
self.db_by_name[db_name] = db

Check warning on line 116 in odoosh_queue_job/__init__.py

View check run for this annotation

Codecov / codecov/patch

odoosh_queue_job/__init__.py#L116

Added line #L116 was not covered by tests
# Upstream code
# with db.select_jobs("state in %s", (NOT_DONE,)) as cr:
# for job_data in cr:
# self.channel_manager.notify(db.db_name, *job_data)
# _logger.info("queue job runner ready for db %s", db.db_name)

def db_check_leader(self):
leader = False

Check warning on line 124 in odoosh_queue_job/__init__.py

View check run for this annotation

Codecov / codecov/patch

odoosh_queue_job/__init__.py#L124

Added line #L124 was not covered by tests
for db in self.db_by_name.values():
leader = db.check_leader()

Check warning on line 126 in odoosh_queue_job/__init__.py

View check run for this annotation

Codecov / codecov/patch

odoosh_queue_job/__init__.py#L126

Added line #L126 was not covered by tests
if leader:
break
return leader

Check warning on line 129 in odoosh_queue_job/__init__.py

View check run for this annotation

Codecov / codecov/patch

odoosh_queue_job/__init__.py#L128-L129

Added lines #L128 - L129 were not covered by tests

def run(self):
_logger.info("starting")

Check warning on line 132 in odoosh_queue_job/__init__.py

View check run for this annotation

Codecov / codecov/patch

odoosh_queue_job/__init__.py#L132

Added line #L132 was not covered by tests
while not self._stop:
# outer loop does exception recovery
try:
_logger.info("initializing database connections")

Check warning on line 136 in odoosh_queue_job/__init__.py

View check run for this annotation

Codecov / codecov/patch

odoosh_queue_job/__init__.py#L135-L136

Added lines #L135 - L136 were not covered by tests
# TODO: how to detect new databases or databases
# on which queue_job is installed after server start?

# Pledra Cust starts here
self.initialize_databases()

Check warning on line 141 in odoosh_queue_job/__init__.py

View check run for this annotation

Codecov / codecov/patch

odoosh_queue_job/__init__.py#L141

Added line #L141 was not covered by tests
while not self._stop:
leader = self.db_check_leader()

Check warning on line 143 in odoosh_queue_job/__init__.py

View check run for this annotation

Codecov / codecov/patch

odoosh_queue_job/__init__.py#L143

Added line #L143 was not covered by tests
if not leader:
time.sleep(LEADER_CHECK_DELAY)
continue

Check warning on line 146 in odoosh_queue_job/__init__.py

View check run for this annotation

Codecov / codecov/patch

odoosh_queue_job/__init__.py#L145-L146

Added lines #L145 - L146 were not covered by tests
else:
break
self.setup_databases()

Check warning on line 149 in odoosh_queue_job/__init__.py

View check run for this annotation

Codecov / codecov/patch

odoosh_queue_job/__init__.py#L148-L149

Added lines #L148 - L149 were not covered by tests
# Pledra Cust ends here
_logger.info("database connections ready")

Check warning on line 151 in odoosh_queue_job/__init__.py

View check run for this annotation

Codecov / codecov/patch

odoosh_queue_job/__init__.py#L151

Added line #L151 was not covered by tests
# inner loop does the normal processing
while not self._stop:
self.process_notifications()
self.run_jobs()
self.wait_notification()

Check warning on line 156 in odoosh_queue_job/__init__.py

View check run for this annotation

Codecov / codecov/patch

odoosh_queue_job/__init__.py#L154-L156

Added lines #L154 - L156 were not covered by tests
except KeyboardInterrupt:
self.stop()

Check warning on line 158 in odoosh_queue_job/__init__.py

View check run for this annotation

Codecov / codecov/patch

odoosh_queue_job/__init__.py#L158

Added line #L158 was not covered by tests
except InterruptedError:
# Interrupted system call, i.e. KeyboardInterrupt during select
self.stop()
except Exception:
_logger.exception(

Check warning on line 163 in odoosh_queue_job/__init__.py

View check run for this annotation

Codecov / codecov/patch

odoosh_queue_job/__init__.py#L161-L163

Added lines #L161 - L163 were not covered by tests
"exception: sleeping %ds and retrying", ERROR_RECOVERY_DELAY
)
self.close_databases()
time.sleep(ERROR_RECOVERY_DELAY)
self.close_databases(remove_jobs=False)
_logger.info("stopped")

Check warning on line 169 in odoosh_queue_job/__init__.py

View check run for this annotation

Codecov / codecov/patch

odoosh_queue_job/__init__.py#L166-L169

Added lines #L166 - L169 were not covered by tests

jobrunner._start_runner_thread = _start_runner_thread

jobrunner.runner._connection_info_for = _connection_info_for

jobrunner.runner.Database.__init__ = _init__
jobrunner.runner.Database.check_leader = check_leader

jobrunner.runner.QueueJobRunner.run = run
jobrunner.runner.QueueJobRunner.initialize_databases = initialize_databases
jobrunner.runner.QueueJobRunner.db_check_leader = db_check_leader
jobrunner.runner.QueueJobRunner.setup_databases = setup_databases


main()
12 changes: 12 additions & 0 deletions odoosh_queue_job/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "Queue job for odoo.sh",
"author": "Pledra",
"license": "AGPL-3",
"website": "https://pledra.com",
"category": "Generic Modules",
"version": "16.0.0.0.0",
"depends": [
"base",
"queue_job"
]
}

0 comments on commit 2e5beb8

Please sign in to comment.