Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.22.0
0.22.1
39 changes: 39 additions & 0 deletions conreq/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Monkey patch Huey's filelock implementation to add Windows support.
import os
import sys


class FileLock:
"""Operating system agnostic file lock implementation using os.open and os.close."""

def __init__(self, filename):
self.filename = filename
self.fd = None

dirname = os.path.dirname(filename)
if not os.path.exists(dirname):
os.makedirs(dirname)
elif os.path.exists(self.filename):
Comment on lines +13 to +16
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Unconditionally deleting an existing lock file in init can interfere with other processes and may raise errors for empty dirnames.

Two issues here:

  1. Deleting self.filename in __init__ can remove an active lock held by another process and break locking semantics. A lock implementation should not unconditionally delete a potentially valid lock file without verifying ownership.

  2. If filename has no directory (e.g. "lockfile"), os.path.dirname(filename) is '', so os.makedirs(dirname) will be called with an empty string and fail. Guard against this with something like if dirname and not os.path.exists(dirname): and reconsider whether the eager unlink is needed here at all.

os.unlink(self.filename)

def acquire(self):
flags = os.O_CREAT | os.O_TRUNC | os.O_RDWR
self.fd = os.open(self.filename, flags)
Comment on lines +19 to +21
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Current FileLock.acquire implementation does not provide actual mutual exclusion across processes.

Because the file is opened with os.O_CREAT | os.O_TRUNC | os.O_RDWR only, multiple processes can open the same lock file at once, so this does not enforce mutual exclusion (especially on Windows, where several processes can think they hold the lock). Please use os.O_EXCL or a platform-specific locking primitive (msvcrt.locking on Windows, fcntl on POSIX, etc.) so acquisition fails or blocks when another process already holds the lock, matching Huey’s FileLock semantics.


def release(self):
if self.fd is not None:
fd, self.fd = self.fd, None
os.close(fd)

def __enter__(self):
self.acquire()
return self

def __exit__(self, exc_type, exc_val, exc_tb):
self.release()


if sys.platform == "win32":
import huey.utils

huey.utils.FileLock = FileLock
7 changes: 0 additions & 7 deletions conreq/core/base/management/commands/preconfig_conreq.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
BASE_DIR = getattr(settings, "BASE_DIR")
DATA_DIR = getattr(settings, "DATA_DIR")
DATABASES = getattr(settings, "DATABASES")
HUEY_FILENAME = getattr(settings, "HUEY_FILENAME")


class Command(BaseCommand):
Expand All @@ -34,12 +33,6 @@ def handle(self, *args, **options):
database = DATABASES["default"]["NAME"]
self.setup_sqlite_database(database, "Conreq", uid, gid, no_perms)

# Background task database
if HUEY_FILENAME:
self.setup_sqlite_database(
HUEY_FILENAME, "Background Task", uid, gid, no_perms
)

if DEBUG:
# Migrate silk due to their wonky dev choices
call_command("makemigrations", "silk")
Expand Down
1 change: 0 additions & 1 deletion conreq/core/base/management/commands/run_conreq.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

UVICORN_CONFIG = os.path.join(getattr(settings, "DATA_DIR"), "uvicorn.env")
DEBUG = get_debug()
HUEY_FILENAME = getattr(settings, "HUEY_FILENAME")
ACCESS_LOG_FILE = getattr(settings, "ACCESS_LOG_FILE")

_logger = getLogger(__name__)
Expand Down
1 change: 0 additions & 1 deletion conreq/core/base/static/js/events_click.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,6 @@ var quick_request_click_event = async function () {
// Request the content
post_json(btn.data("request-url"), params, function () {
requested_toast_message();
console.log(btn);
btn.remove();
ongoing_request = null;
}).fail(async function () {
Expand Down
27 changes: 0 additions & 27 deletions conreq/core/base/tasks.py
Original file line number Diff line number Diff line change
@@ -1,37 +1,10 @@
import sqlite3

from django.conf import settings
from django.db import connection
from huey import crontab
from huey.contrib.djhuey import db_periodic_task

from conreq.utils.environment import get_database_type

DB_ENGINE = get_database_type()
HUEY_FILENAME = getattr(settings, "HUEY_FILENAME")


@db_periodic_task(crontab(minute="0", hour="0", strict=True), expires=120)
def huey_db_maintenance():
with sqlite3.connect(HUEY_FILENAME) as cursor:
cursor.execute(
# Only keep the 1000 latest tasks
"""DELETE FROM task
WHERE id NOT IN (
SELECT id
FROM (
SELECT id
FROM task
ORDER BY id DESC
LIMIT 1000
) foo
);
"""
)
with sqlite3.connect(HUEY_FILENAME) as cursor:
cursor.execute("PRAGMA optimize;")
cursor.execute("VACUUM;")
cursor.execute("REINDEX;")


if DB_ENGINE == "SQLITE3":
Expand Down
20 changes: 5 additions & 15 deletions conreq/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,25 +90,15 @@
"css": ["compressor.filters.cssmin.rCSSMinFilter"],
"js": ["compressor.filters.jsmin.JSMinFilter"],
}
HUEY_FILENAME = os.path.join(DATA_DIR, "bg_tasks.sqlite3")
HUEY = {
"name": "huey", # DB name for huey.
"huey_class": "huey.SqliteHuey", # Huey implementation to use.
"filename": HUEY_FILENAME, # Sqlite filename
"results": True, # Whether to return values of tasks.
"store_none": False, # Whether to store results of tasks that return None.
"immediate": False, # If True, run tasks synchronously.
"strict_fifo": True, # Utilize Sqlite AUTOINCREMENT to have unique task IDs
"timeout": 10, # Seconds to wait when reading from the DB.
"connection": {
"isolation_level": "IMMEDIATE", # Use immediate transactions to allow sqlite to respect `timeout`.
"cached_statements": 2000, # Number of pages to keep in memory.
},
"name": "huey",
"huey_class": "huey.FileHuey",
"path": os.path.join(DATA_DIR, "tasks"),
"immediate": False,
"use_thread_lock": True,
"consumer": {
"workers": os.cpu_count() or 8, # Number of worker processes/threads.
"worker_type": "thread", # "thread" or "process"
"initial_delay": 0.25, # Smallest polling interval
"check_worker_health": True, # Whether to monitor worker health.
},
}

Expand Down
2 changes: 1 addition & 1 deletion requirements/main.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ titlecase==2.4.1
tmdbsimple==2.9.1
Twisted[tls,http2]==25.5.0
tzlocal==5.3.1
servestatic[brotli]==3.1.0
servestatic[brotli]==4.1.0
uvicorn[standard]==0.38.0
attrs
cffi
Loading