ArabicNewsAnalyzer commited on
Commit
ead8f13
·
verified ·
1 Parent(s): 89f792d

Update app/memory/redis_checkpointer.py

Browse files
Files changed (1) hide show
  1. app/memory/redis_checkpointer.py +31 -6
app/memory/redis_checkpointer.py CHANGED
@@ -1,5 +1,8 @@
1
  from __future__ import annotations
2
 
 
 
 
3
  from redis.asyncio import Redis
4
  from redis.asyncio.retry import Retry
5
  from redis.backoff import ExponentialBackoff
@@ -9,17 +12,19 @@ from langgraph.checkpoint.redis.aio import AsyncRedisSaver
9
 
10
  from app.config import get_settings
11
 
 
 
12
  _checkpointer_cm = None
13
  _checkpointer: AsyncRedisSaver | None = None
14
  _redis_client: Redis | None = None
 
15
 
16
 
17
  def _build_redis_client(redis_url: str) -> Redis:
18
  return Redis.from_url(
19
  redis_url,
20
- # Detect and replace dead connections proactively
21
- health_check_interval=30, # ping idle connections every 30s
22
- socket_keepalive=True, # TCP-level keepalive
23
  socket_connect_timeout=5,
24
  socket_timeout=10,
25
  retry_on_timeout=True,
@@ -28,8 +33,23 @@ def _build_redis_client(redis_url: str) -> Redis:
28
  )
29
 
30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  async def get_checkpointer() -> AsyncRedisSaver:
32
- global _checkpointer_cm, _checkpointer, _redis_client
33
  if _checkpointer is None:
34
  settings = get_settings()
35
  ttl_minutes = max(1, settings.chat_session_ttl_seconds // 60)
@@ -42,15 +62,20 @@ async def get_checkpointer() -> AsyncRedisSaver:
42
  )
43
  _checkpointer = await _checkpointer_cm.__aenter__()
44
  await _checkpointer.asetup()
 
 
45
  return _checkpointer
46
 
47
 
48
  async def close_checkpointer() -> None:
49
- global _checkpointer_cm, _checkpointer, _redis_client
 
 
50
  if _checkpointer_cm is not None:
51
  await _checkpointer_cm.__aexit__(None, None, None)
52
  if _redis_client is not None:
53
  await _redis_client.aclose()
54
  _checkpointer = None
55
  _checkpointer_cm = None
56
- _redis_client = None
 
 
1
  from __future__ import annotations
2
 
3
+ import asyncio
4
+ import logging
5
+
6
  from redis.asyncio import Redis
7
  from redis.asyncio.retry import Retry
8
  from redis.backoff import ExponentialBackoff
 
12
 
13
  from app.config import get_settings
14
 
15
+ logger = logging.getLogger(__name__)
16
+
17
  _checkpointer_cm = None
18
  _checkpointer: AsyncRedisSaver | None = None
19
  _redis_client: Redis | None = None
20
+ _heartbeat_task: asyncio.Task | None = None
21
 
22
 
23
  def _build_redis_client(redis_url: str) -> Redis:
24
  return Redis.from_url(
25
  redis_url,
26
+ health_check_interval=15,
27
+ socket_keepalive=True,
 
28
  socket_connect_timeout=5,
29
  socket_timeout=10,
30
  retry_on_timeout=True,
 
33
  )
34
 
35
 
36
+ async def _redis_heartbeat(interval: int = 15):
37
+ """Keeps the pooled Redis connection alive by sending real traffic
38
+ through it periodically — Railway's proxy kills idle connections,
39
+ and OS-level TCP keepalive alone doesn't count as activity to it."""
40
+ while True:
41
+ try:
42
+ await asyncio.sleep(interval)
43
+ if _redis_client is not None:
44
+ await _redis_client.ping()
45
+ except asyncio.CancelledError:
46
+ break
47
+ except Exception as e:
48
+ logger.warning("Redis heartbeat ping failed: %s", e)
49
+
50
+
51
  async def get_checkpointer() -> AsyncRedisSaver:
52
+ global _checkpointer_cm, _checkpointer, _redis_client, _heartbeat_task
53
  if _checkpointer is None:
54
  settings = get_settings()
55
  ttl_minutes = max(1, settings.chat_session_ttl_seconds // 60)
 
62
  )
63
  _checkpointer = await _checkpointer_cm.__aenter__()
64
  await _checkpointer.asetup()
65
+
66
+ _heartbeat_task = asyncio.create_task(_redis_heartbeat())
67
  return _checkpointer
68
 
69
 
70
  async def close_checkpointer() -> None:
71
+ global _checkpointer_cm, _checkpointer, _redis_client, _heartbeat_task
72
+ if _heartbeat_task is not None:
73
+ _heartbeat_task.cancel()
74
  if _checkpointer_cm is not None:
75
  await _checkpointer_cm.__aexit__(None, None, None)
76
  if _redis_client is not None:
77
  await _redis_client.aclose()
78
  _checkpointer = None
79
  _checkpointer_cm = None
80
+ _redis_client = None
81
+ _heartbeat_task = None