Skip to content

Commit c074e52

Browse files
authored
fix: redis connections leak (#148)
- fix: bump saq lib to 0.12 to avoid Redis connections leak - chore: bump project dependencies versions - chore: move to the alpine version of the docker image
1 parent 0bb3a60 commit c074e52

11 files changed

Lines changed: 385 additions & 354 deletions

File tree

.github/workflows/tests.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ jobs:
2424
run: |
2525
pip install copier==7.1.0
2626
pip install copier-templates-extensions
27+
pip install pyyaml-include==1.4.1
2728
# hardcode pydantic version to avoid dependency conflict with copier
2829
pip install pydantic==1.10.2
2930
@@ -72,6 +73,7 @@ jobs:
7273
run: |
7374
pip install copier==7.1.0
7475
pip install copier-templates-extensions
76+
pip install pyyaml-include==1.4.1
7577
# hardcode pydantic version to avoid dependency conflict with copier
7678
pip install pydantic==1.10.2
7779
python -m copier --defaults . bot-example
@@ -127,6 +129,7 @@ jobs:
127129
run: |
128130
pip install copier==7.1.0
129131
pip install copier-templates-extensions
132+
pip install pyyaml-include==1.4.1
130133
# hardcode pydantic version to avoid dependency conflict with copier
131134
pip install pydantic==1.10.2
132135
python -m copier --defaults . bot-example

Dockerfile.jinja

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,38 @@
1-
FROM python:3.10-slim
1+
FROM python:3.11.5-alpine
22

33
{% if from_ccsteam %}LABEL Maintainer="eXpress Unlimited Production"{% endif %}
44

5-
ENV PYTHONUNBUFFERED 1
6-
ENV UVICORN_CMD_ARGS ""
7-
8-
EXPOSE 8000
9-
105
# Install system-wide dependencies
11-
RUN apt-get update && \
12-
apt-get install --no-install-recommends -y git curl gcc && \
13-
python3 -m pip install setuptools && \
14-
apt-get clean autoclean && \
15-
apt-get autoremove --yes && \
16-
rm -rf /var/lib/apt/lists/*
6+
RUN apk update && \
7+
apk add --no-cache --clean-protected git curl gcc python3-dev && \
8+
rm -rf /var/cache/apk/*
179

1810
# Create user for app
1911
ENV APP_USER=appuser
20-
RUN useradd --create-home $APP_USER
12+
RUN adduser -D $APP_USER
2113
WORKDIR /home/$APP_USER
2214
USER $APP_USER
2315

16+
# Set python env vars
17+
ENV PYTHONUNBUFFERED 1
18+
ENV PYTHONNODEBUGRANGES 1
19+
ENV PYTHONDONTWRITEBYTECODE 1
20+
ENV PYTHONPATH="/home/$APP_USER:$PYTHONPATH"
21+
2422
# Use venv directly via PATH
2523
ENV VENV_PATH=/home/$APP_USER/.venv/bin
2624
ENV USER_PATH=/home/$APP_USER/.local/bin
2725
ENV PATH="$VENV_PATH:$USER_PATH:$PATH"
2826

29-
RUN pip install --user --no-cache-dir poetry==1.2.2 && \
30-
poetry config virtualenvs.in-project true
27+
# Set app env vars
28+
ENV GUNICORN_CMD_ARGS ""
29+
30+
# Set build env vars
31+
ARG CI_COMMIT_SHA=""
32+
ENV GIT_COMMIT_SHA=${CI_COMMIT_SHA}
33+
34+
RUN pip install --user --no-cache-dir poetry==1.4.2 && \
35+
poetry config virtualenvs.in-project true
3136

3237
COPY poetry.lock pyproject.toml ./
3338

@@ -38,17 +43,16 @@ ARG GIT_PASSWORD=${CI_JOB_TOKEN}
3843
ARG GIT_LOGIN="gitlab-ci-token"
3944
# Poetry can't read password to download private repos
4045
RUN echo -e "machine ${GIT_HOST}\nlogin ${GIT_LOGIN}\npassword ${GIT_PASSWORD}" > ~/.netrc && \
41-
poetry install --only main && \
42-
rm -rf ~/.netrc
46+
poetry install --only main && \
47+
rm -rf ~/.netrc
4348
{% else %}
4449
RUN poetry install --only main
4550
{% endif %}
4651

4752
COPY alembic.ini .
4853
COPY app app
4954

50-
ARG CI_COMMIT_SHA=""
51-
ENV GIT_COMMIT_SHA=${CI_COMMIT_SHA}
55+
EXPOSE 8000
5256

5357
CMD alembic upgrade head && \
54-
gunicorn "app.main:get_application()" --worker-class uvicorn.workers.UvicornWorker --bind 0.0.0.0
58+
gunicorn "app.main:get_application()" --worker-class uvicorn.workers.UvicornWorker --bind 0.0.0.0 $GUNICORN_CMD_ARGS

app/api/dependencies/healthcheck.py.jinja

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ check_redis_connection_dependency = Depends(check_redis_connection)
4747
async def check_worker_status() -> Optional[str]:
4848
job = await queue.enqueue("healthcheck")
4949

50+
if not job:
51+
return None
52+
5053
try:
5154
await job.refresh(settings.WORKER_TIMEOUT_SEC)
5255
except TimeoutError:

app/caching/exception_handlers.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
"""Redis custom exception handlers."""
22

3-
from redis.asyncio.client import PubSub
3+
from redis.asyncio.client import PubSub, PubsubWorkerExceptionHandler
44

55
from app.logger import logger
66

77

8-
async def pubsub_exception_handler(exc: BaseException, pubsub: PubSub) -> None:
9-
logger.exception("Something went wrong in PubSub")
8+
class PubsubExceptionHandler(PubsubWorkerExceptionHandler):
9+
def __call__(self, exc: BaseException, pubsub: PubSub) -> None:
10+
logger.exception("Something went wrong in PubSub")

app/main.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from app.api.routers import router
1111
from app.bot.bot import get_bot
1212
from app.caching.callback_redis_repo import CallbackRedisRepo
13-
from app.caching.exception_handlers import pubsub_exception_handler
13+
from app.caching.exception_handlers import PubsubExceptionHandler
1414
from app.caching.redis_repo import RedisRepo
1515
from app.db.sqlalchemy import build_db_session_factory, close_db_connections
1616
from app.resources import strings
@@ -33,7 +33,7 @@ async def startup(application: FastAPI, raise_bot_exceptions: bool) -> None:
3333
# -- Bot --
3434
callback_repo = CallbackRedisRepo(redis_client)
3535
process_callbacks_task = asyncio.create_task(
36-
callback_repo.pubsub.run(exception_handler=pubsub_exception_handler)
36+
callback_repo.pubsub.run(exception_handler=PubsubExceptionHandler())
3737
)
3838
bot = get_bot(callback_repo, raise_exceptions=raise_bot_exceptions)
3939

app/services/botx_user_search.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@
33
from typing import Optional, Tuple
44
from uuid import UUID
55

6-
from pybotx import Bot, BotAccount, UserFromSearch, UserKinds, UserNotFoundError
6+
from pybotx import (
7+
Bot,
8+
BotAccountWithSecret,
9+
UserFromSearch,
10+
UserKinds,
11+
UserNotFoundError,
12+
)
713

814

915
class UserIsBotError(Exception):
@@ -12,7 +18,7 @@ class UserIsBotError(Exception):
1218

1319
async def search_user_on_each_cts(
1420
bot: Bot, huid: UUID
15-
) -> Optional[Tuple[UserFromSearch, BotAccount]]:
21+
) -> Optional[Tuple[UserFromSearch, BotAccountWithSecret]]:
1622
"""Search user by huid on all cts on which bot is registered.
1723
1824
return type: tuple of UserFromSearch instance and host.

app/settings.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,15 @@ def _build_credentials_from_string(
4141
credentials_str = credentials_str.replace("|", "@")
4242
assert credentials_str.count("@") == 2, "Have you forgot to add `bot_id`?"
4343

44-
host, secret_key, bot_id = [
44+
cts_url, secret_key, bot_id = [
4545
str_value.strip() for str_value in credentials_str.split("@")
4646
]
47+
48+
if "://" not in cts_url:
49+
cts_url = f"https://{cts_url}"
50+
4751
return BotAccountWithSecret(
48-
id=UUID(bot_id), host=host, secret_key=secret_key
52+
id=UUID(bot_id), cts_url=cts_url, secret_key=secret_key
4953
)
5054

5155
BOT_CREDENTIALS: List[BotAccountWithSecret]

docker-compose.yml.jinja

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ services:
3232
build: .
3333
container_name: {{bot_project_name}}-worker
3434
# '$$' prevents docker-compose from interpolating a value
35-
command: bash -c 'PYTHONPATH="$$PYTHONPATH:$$PWD" saq app.worker.worker.settings'
35+
command: /bin/sh -c 'PYTHONPATH="$$PYTHONPATH:$$PWD" saq app.worker.worker.settings'
3636
environment: *environment
3737
restart: always
3838
depends_on: *depends_on

0 commit comments

Comments
 (0)