|
16 | 16 | from models_library.projects import ProjectID, ProjectTemplateType |
17 | 17 | from models_library.projects import ProjectType as ProjectTypeAPI |
18 | 18 | from servicelib.logging_errors import create_troubleshootting_log_kwargs |
| 19 | +from servicelib.logging_utils import log_context |
19 | 20 | from servicelib.redis import ( |
20 | 21 | PROJECT_DB_UPDATE_REDIS_LOCK_KEY, |
21 | 22 | exclusive, |
@@ -102,99 +103,101 @@ async def remove_project_documents_as_admin(app: web.Application) -> None: |
102 | 103 | checks if there are any users currently connected to the project room via socketio, |
103 | 104 | and removes documents that have no connected users. |
104 | 105 | """ |
105 | | - # Get Redis document manager client to access the DOCUMENTS database |
106 | | - redis_client = get_redis_document_manager_client_sdk(app) |
| 106 | + with log_context( |
| 107 | + _logger, |
| 108 | + logging.INFO, |
| 109 | + msg="Project document cleanup started", |
| 110 | + ): |
| 111 | + # Get Redis document manager client to access the DOCUMENTS database |
| 112 | + redis_client = get_redis_document_manager_client_sdk(app) |
107 | 113 |
|
108 | | - # Pattern to match project document keys - looking for keys that contain project UUIDs |
109 | | - project_document_pattern = "projects:*:version" |
| 114 | + # Pattern to match project document keys - looking for keys that contain project UUIDs |
| 115 | + project_document_pattern = "projects:*:version" |
110 | 116 |
|
111 | | - # Get socketio server instance |
112 | | - sio = get_socket_server(app) |
| 117 | + # Get socketio server instance |
| 118 | + sio = get_socket_server(app) |
113 | 119 |
|
114 | | - # Get known opened projects ids based on Redis resources table |
115 | | - registry = get_registry(app) |
116 | | - known_opened_project_ids = await list_opened_project_ids(registry) |
117 | | - known_opened_project_ids_set = set(known_opened_project_ids) |
| 120 | + # Get known opened projects ids based on Redis resources table |
| 121 | + registry = get_registry(app) |
| 122 | + known_opened_project_ids = await list_opened_project_ids(registry) |
| 123 | + known_opened_project_ids_set = set(known_opened_project_ids) |
118 | 124 |
|
119 | | - projects_removed = 0 |
| 125 | + projects_removed = 0 |
120 | 126 |
|
121 | | - # Scan through all project document keys |
122 | | - async for key in redis_client.redis.scan_iter( |
123 | | - match=project_document_pattern, count=1000 |
124 | | - ): |
125 | | - # Extract project UUID from the key pattern "projects:{project_uuid}:version" |
126 | | - key_str = key.decode("utf-8") if isinstance(key, bytes) else key |
127 | | - match = re.match(r"projects:(?P<project_uuid>[0-9a-f-]+):version", key_str) |
128 | | - |
129 | | - if not match: |
130 | | - continue |
131 | | - |
132 | | - project_uuid_str = match.group("project_uuid") |
133 | | - project_uuid = ProjectID(project_uuid_str) |
134 | | - project_room = SocketIORoomStr.from_project_id(project_uuid) |
135 | | - |
136 | | - # 1. CHECK - Check if the project UUID is in the known opened projects |
137 | | - if project_uuid in known_opened_project_ids_set: |
138 | | - _logger.debug( |
139 | | - "Project %s is in Redis Resources table (which means Project is opened), keeping document", |
140 | | - project_uuid, |
141 | | - ) |
142 | | - continue |
143 | | - |
144 | | - # 2. CHECK - Check if there are any users connected to this project room |
145 | | - try: |
146 | | - # Get all session IDs (socket IDs) in the project room |
147 | | - room_sessions = list( |
148 | | - sio.manager.get_participants(namespace="/", room=project_room) |
149 | | - ) |
150 | | - |
151 | | - # If no users are connected to this project room, remove the document |
152 | | - if not room_sessions: |
153 | | - await redis_client.redis.delete(key_str) |
154 | | - projects_removed += 1 |
155 | | - _logger.info( |
156 | | - "Removed project document for project %s (no connected users)", |
| 127 | + # Scan through all project document keys |
| 128 | + async for key in redis_client.redis.scan_iter( |
| 129 | + match=project_document_pattern, count=1000 |
| 130 | + ): |
| 131 | + # Extract project UUID from the key pattern "projects:{project_uuid}:version" |
| 132 | + key_str = key.decode("utf-8") if isinstance(key, bytes) else key |
| 133 | + match = re.match(r"projects:(?P<project_uuid>[0-9a-f-]+):version", key_str) |
| 134 | + |
| 135 | + if not match: |
| 136 | + continue |
| 137 | + |
| 138 | + project_uuid_str = match.group("project_uuid") |
| 139 | + project_uuid = ProjectID(project_uuid_str) |
| 140 | + project_room = SocketIORoomStr.from_project_id(project_uuid) |
| 141 | + |
| 142 | + # 1. CHECK - Check if the project UUID is in the known opened projects |
| 143 | + if project_uuid in known_opened_project_ids_set: |
| 144 | + _logger.debug( |
| 145 | + "Project %s is in Redis Resources table (which means Project is opened), keeping document", |
157 | 146 | project_uuid, |
158 | 147 | ) |
159 | | - else: |
160 | | - # Create a synthetic exception for this unexpected state |
161 | | - unexpected_state_error = RuntimeError( |
162 | | - f"Project {project_uuid} has {len(room_sessions)} connected users but is not in Redis Resources table" |
| 148 | + continue |
| 149 | + |
| 150 | + # 2. CHECK - Check if there are any users connected to this project room |
| 151 | + try: |
| 152 | + # Get all session IDs (socket IDs) in the project room |
| 153 | + room_sessions = list( |
| 154 | + sio.manager.get_participants(namespace="/", room=project_room) |
163 | 155 | ) |
164 | | - _logger.error( |
| 156 | + |
| 157 | + # If no users are connected to this project room, remove the document |
| 158 | + if not room_sessions: |
| 159 | + await redis_client.redis.delete(key_str) |
| 160 | + projects_removed += 1 |
| 161 | + _logger.info( |
| 162 | + "Removed project document for project %s (no connected users)", |
| 163 | + project_uuid, |
| 164 | + ) |
| 165 | + else: |
| 166 | + # Create a synthetic exception for this unexpected state |
| 167 | + unexpected_state_error = RuntimeError( |
| 168 | + f"Project {project_uuid} has {len(room_sessions)} connected users but is not in Redis Resources table" |
| 169 | + ) |
| 170 | + _logger.error( |
| 171 | + **create_troubleshootting_log_kwargs( |
| 172 | + user_error_msg=f"Project {project_uuid} has {len(room_sessions)} connected users in the socket io room (This is not expected, as project resource is not in the Redis Resources table), keeping document just in case", |
| 173 | + error=unexpected_state_error, |
| 174 | + error_context={ |
| 175 | + "project_uuid": str(project_uuid), |
| 176 | + "project_room": project_room, |
| 177 | + "key_str": key_str, |
| 178 | + "connected_users_count": len(room_sessions), |
| 179 | + "room_sessions": room_sessions[ |
| 180 | + :5 |
| 181 | + ], # Limit to first 5 sessions for debugging |
| 182 | + }, |
| 183 | + tip="This indicates a potential race condition or inconsistency between the Redis Resources table and socketio room state. Check if the project was recently closed but users are still connected, or if there's a synchronization issue between services.", |
| 184 | + ) |
| 185 | + ) |
| 186 | + continue |
| 187 | + |
| 188 | + except (KeyError, AttributeError, ValueError) as exc: |
| 189 | + _logger.exception( |
165 | 190 | **create_troubleshootting_log_kwargs( |
166 | | - user_error_msg=f"Project {project_uuid} has {len(room_sessions)} connected users in the socket io room (This is not expected, as project resource is not in the Redis Resources table), keeping document just in case", |
167 | | - error=unexpected_state_error, |
| 191 | + user_error_msg=f"Failed to check room participants for project {project_uuid}", |
| 192 | + error=exc, |
168 | 193 | error_context={ |
169 | 194 | "project_uuid": str(project_uuid), |
170 | 195 | "project_room": project_room, |
171 | 196 | "key_str": key_str, |
172 | | - "connected_users_count": len(room_sessions), |
173 | | - "room_sessions": room_sessions[ |
174 | | - :5 |
175 | | - ], # Limit to first 5 sessions for debugging |
176 | 197 | }, |
177 | | - tip="This indicates a potential race condition or inconsistency between the Redis Resources table and socketio room state. Check if the project was recently closed but users are still connected, or if there's a synchronization issue between services.", |
| 198 | + tip="Check if socketio server is properly initialized and the room exists. This could indicate a socketio manager issue or invalid room format.", |
178 | 199 | ) |
179 | 200 | ) |
180 | 201 | continue |
181 | 202 |
|
182 | | - except (KeyError, AttributeError, ValueError) as exc: |
183 | | - _logger.exception( |
184 | | - **create_troubleshootting_log_kwargs( |
185 | | - user_error_msg=f"Failed to check room participants for project {project_uuid}", |
186 | | - error=exc, |
187 | | - error_context={ |
188 | | - "project_uuid": str(project_uuid), |
189 | | - "project_room": project_room, |
190 | | - "key_str": key_str, |
191 | | - }, |
192 | | - tip="Check if socketio server is properly initialized and the room exists. This could indicate a socketio manager issue or invalid room format.", |
193 | | - ) |
194 | | - ) |
195 | | - continue |
196 | | - |
197 | | - _logger.info( |
198 | | - "Project document cleanup completed: removed %d project documents", |
199 | | - projects_removed, |
200 | | - ) |
| 203 | + _logger.info("Completed: removed %d project documents", projects_removed) |
0 commit comments