add life span manager to guarantee graceful db shutdown

parent 5b55ec50
...@@ -268,4 +268,4 @@ if __name__ == "__main__": ...@@ -268,4 +268,4 @@ if __name__ == "__main__":
print("🔍 Verifying Setup") print("🔍 Verifying Setup")
# Verify the setup # Verify the setup
verify_curriculum_structure() verify_curriculum_structure()
\ No newline at end of file
This diff is collapsed.
...@@ -293,6 +293,3 @@ class AgentService: ...@@ -293,6 +293,3 @@ class AgentService:
except Exception as e: except Exception as e:
logger.error(f"Error closing connection pools: {e}") logger.error(f"Error closing connection pools: {e}")
def __del__(self):
"""Destructor to ensure connection pools are closed"""
self.close()
\ No newline at end of file
...@@ -40,43 +40,44 @@ class ResponseManager: ...@@ -40,43 +40,44 @@ class ResponseManager:
def get_response(self, student_id: str) -> Dict: def get_response(self, student_id: str) -> Dict:
""" """
Atomically gets the response for a student and removes it from Redis Gets the response for a student without deleting it.
to ensure it's claimed only once. This allows the client to safely retry the request if it fails.
The key will be cleaned up automatically by Redis when its TTL expires.
""" """
key = self._get_key(student_id) key = self._get_key(student_id)
# Use a pipeline to get and delete the key in a single, atomic operation # 1. Use a simple, non-destructive GET command. No pipeline needed.
pipe = self.redis.pipeline() json_value = self.redis.get(key)
pipe.get(key)
pipe.delete(key)
results = pipe.execute()
json_value = results[0]
if not json_value: if not json_value:
# If nothing was found, return the same empty structure as the old class return {"text": None, "audio_filepath": None, "audio_bytes": None}
return {"text": None, "audio_filename": None, "audio_bytes": None}
# If data was found, decode it # 2. Decode the payload as before.
payload = json.loads(json_value) payload = json.loads(json_value)
# Decode the Base64 string back into binary audio data
if payload.get("audio_bytes_b64"): if payload.get("audio_bytes_b64"):
payload["audio_bytes"] = base64.b64decode(payload["audio_bytes_b64"]) payload["audio_bytes"] = base64.b64decode(payload["audio_bytes_b64"])
else: else:
payload["audio_bytes"] = None payload["audio_bytes"] = None
# Remove the temporary key before returning
del payload["audio_bytes_b64"] del payload["audio_bytes_b64"]
return payload return payload
def clear_response(self, student_id: str) -> None: def clear_response(self, student_id: str) -> None:
"""Clears a response for a specific student from Redis.""" """
Clears a response for a specific student from Redis.
This is still important to call at the *beginning* of a new /chat request
to ensure old data is invalidated immediately.
"""
key = self._get_key(student_id) key = self._get_key(student_id)
self.redis.delete(key) self.redis.delete(key)
def is_response_fresh(self, student_id: str, max_age_seconds: int = 300) -> bool: def is_response_fresh(self, student_id: str) -> bool:
"""Checks if a response exists in Redis for the given student.""" """
Checks if a response exists in Redis for the given student.
This is much simpler and more reliable now.
"""
key = self._get_key(student_id) key = self._get_key(student_id)
# redis.exists() is the direct equivalent of checking if the key is present # redis.exists() returns the number of keys that exist (0 or 1 in this case).
return self.redis.exists(key) > 0 return self.redis.exists(key) > 0
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment