fix(embedding-server): ensure shutdown-capable ZMQ threads create/bind their own REP sockets and poll with timeouts; fix undefined socket causing startup crash and CI hangs on Ubuntu 22.04
This commit is contained in:
@@ -222,22 +222,33 @@ def create_diskann_embedding_server(
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
def zmq_server_thread_with_shutdown(shutdown_event):
|
def zmq_server_thread_with_shutdown(shutdown_event):
|
||||||
"""ZMQ server thread that respects shutdown signal."""
|
"""ZMQ server thread that respects shutdown signal.
|
||||||
|
|
||||||
|
This creates its own REP socket, binds to zmq_port, and periodically
|
||||||
|
checks shutdown_event using recv timeouts to exit cleanly.
|
||||||
|
"""
|
||||||
logger.info("DiskANN ZMQ server thread started with shutdown support")
|
logger.info("DiskANN ZMQ server thread started with shutdown support")
|
||||||
|
|
||||||
# Set receive timeout so we can check shutdown_event periodically
|
context = zmq.Context()
|
||||||
socket.setsockopt(zmq.RCVTIMEO, 1000) # 1 second timeout
|
rep_socket = context.socket(zmq.REP)
|
||||||
|
rep_socket.bind(f"tcp://*:{zmq_port}")
|
||||||
|
logger.info(f"DiskANN ZMQ REP server listening on port {zmq_port}")
|
||||||
|
|
||||||
|
# Set receive timeout so we can check shutdown_event periodically
|
||||||
|
rep_socket.setsockopt(zmq.RCVTIMEO, 1000) # 1 second timeout
|
||||||
|
rep_socket.setsockopt(zmq.SNDTIMEO, 300000)
|
||||||
|
|
||||||
|
try:
|
||||||
while not shutdown_event.is_set():
|
while not shutdown_event.is_set():
|
||||||
try:
|
try:
|
||||||
e2e_start = time.time()
|
e2e_start = time.time()
|
||||||
# REP socket receives single-part messages
|
# REP socket receives single-part messages
|
||||||
message = socket.recv(zmq.NOBLOCK)
|
message = rep_socket.recv()
|
||||||
|
|
||||||
# Check for empty messages - REP socket requires response to every request
|
# Check for empty messages - REP socket requires response to every request
|
||||||
if not message:
|
if not message:
|
||||||
logger.warning("Received empty message, sending empty response")
|
logger.warning("Received empty message, sending empty response")
|
||||||
socket.send(b"")
|
rep_socket.send(b"")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Try protobuf first (same logic as original)
|
# Try protobuf first (same logic as original)
|
||||||
@@ -252,7 +263,8 @@ def create_diskann_embedding_server(
|
|||||||
# Look up texts by node IDs
|
# Look up texts by node IDs
|
||||||
for nid in node_ids:
|
for nid in node_ids:
|
||||||
try:
|
try:
|
||||||
txt = passages.get_text(nid)
|
passage_data = passages.get_passage(str(nid))
|
||||||
|
txt = passage_data["text"]
|
||||||
if not txt:
|
if not txt:
|
||||||
raise RuntimeError(f"FATAL: Empty text for passage ID {nid}")
|
raise RuntimeError(f"FATAL: Empty text for passage ID {nid}")
|
||||||
texts.append(txt)
|
texts.append(txt)
|
||||||
@@ -271,14 +283,16 @@ def create_diskann_embedding_server(
|
|||||||
):
|
):
|
||||||
texts = request
|
texts = request
|
||||||
is_text_request = True
|
is_text_request = True
|
||||||
logger.info(f"ZMQ received msgpack text request for {len(texts)} texts")
|
logger.info(
|
||||||
|
f"ZMQ received msgpack text request for {len(texts)} texts"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
raise ValueError("Not a valid msgpack text request")
|
raise ValueError("Not a valid msgpack text request")
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.error("Both protobuf and msgpack parsing failed!")
|
logger.error("Both protobuf and msgpack parsing failed!")
|
||||||
# Send error response
|
# Send error response
|
||||||
resp_proto = embedding_pb2.NodeEmbeddingResponse()
|
resp_proto = embedding_pb2.NodeEmbeddingResponse()
|
||||||
socket.send(resp_proto.SerializeToString())
|
rep_socket.send(resp_proto.SerializeToString())
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Process the request
|
# Process the request
|
||||||
@@ -296,7 +310,7 @@ def create_diskann_embedding_server(
|
|||||||
else:
|
else:
|
||||||
resp_proto = embedding_pb2.NodeEmbeddingResponse()
|
resp_proto = embedding_pb2.NodeEmbeddingResponse()
|
||||||
response_data = resp_proto.SerializeToString()
|
response_data = resp_proto.SerializeToString()
|
||||||
socket.send(response_data)
|
rep_socket.send(response_data)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Prepare response based on request type
|
# Prepare response based on request type
|
||||||
@@ -317,7 +331,7 @@ def create_diskann_embedding_server(
|
|||||||
response_data = resp_proto.SerializeToString()
|
response_data = resp_proto.SerializeToString()
|
||||||
|
|
||||||
# Send response back to the client
|
# Send response back to the client
|
||||||
socket.send(response_data)
|
rep_socket.send(response_data)
|
||||||
|
|
||||||
e2e_end = time.time()
|
e2e_end = time.time()
|
||||||
logger.info(f"⏱️ ZMQ E2E time: {e2e_end - e2e_start:.6f}s")
|
logger.info(f"⏱️ ZMQ E2E time: {e2e_end - e2e_start:.6f}s")
|
||||||
@@ -331,12 +345,21 @@ def create_diskann_embedding_server(
|
|||||||
try:
|
try:
|
||||||
# Send error response for REP socket
|
# Send error response for REP socket
|
||||||
resp_proto = embedding_pb2.NodeEmbeddingResponse()
|
resp_proto = embedding_pb2.NodeEmbeddingResponse()
|
||||||
socket.send(resp_proto.SerializeToString())
|
rep_socket.send(resp_proto.SerializeToString())
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
logger.info("Shutdown in progress, ignoring ZMQ error")
|
logger.info("Shutdown in progress, ignoring ZMQ error")
|
||||||
break
|
break
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
rep_socket.close(0)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
context.term()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
logger.info("DiskANN ZMQ server thread exiting gracefully")
|
logger.info("DiskANN ZMQ server thread exiting gracefully")
|
||||||
|
|
||||||
|
|||||||
@@ -241,33 +241,55 @@ def create_hnsw_embedding_server(
|
|||||||
socket.send(msgpack.packb([[], []]))
|
socket.send(msgpack.packb([[], []]))
|
||||||
|
|
||||||
def zmq_server_thread_with_shutdown(shutdown_event):
|
def zmq_server_thread_with_shutdown(shutdown_event):
|
||||||
"""ZMQ server thread that respects shutdown signal."""
|
"""ZMQ server thread that respects shutdown signal.
|
||||||
|
|
||||||
|
Creates its own REP socket bound to zmq_port and polls with timeouts
|
||||||
|
to allow graceful shutdown.
|
||||||
|
"""
|
||||||
logger.info("ZMQ server thread started with shutdown support")
|
logger.info("ZMQ server thread started with shutdown support")
|
||||||
|
|
||||||
# Set receive timeout so we can check shutdown_event periodically
|
context = zmq.Context()
|
||||||
socket.setsockopt(zmq.RCVTIMEO, 1000) # 1 second timeout
|
rep_socket = context.socket(zmq.REP)
|
||||||
|
rep_socket.bind(f"tcp://*:{zmq_port}")
|
||||||
|
logger.info(f"HNSW ZMQ REP server listening on port {zmq_port}")
|
||||||
|
rep_socket.setsockopt(zmq.RCVTIMEO, 1000)
|
||||||
|
rep_socket.setsockopt(zmq.SNDTIMEO, 300000)
|
||||||
|
|
||||||
|
try:
|
||||||
while not shutdown_event.is_set():
|
while not shutdown_event.is_set():
|
||||||
try:
|
try:
|
||||||
e2e_start = time.time()
|
e2e_start = time.time()
|
||||||
logger.debug("🔍 Waiting for ZMQ message...")
|
logger.debug("🔍 Waiting for ZMQ message...")
|
||||||
request_bytes = socket.recv(zmq.NOBLOCK)
|
request_bytes = rep_socket.recv()
|
||||||
|
|
||||||
# Rest of the processing logic (same as original)
|
# Rest of the processing logic (same as original)
|
||||||
request = msgpack.unpackb(request_bytes)
|
request = msgpack.unpackb(request_bytes)
|
||||||
|
|
||||||
if len(request) == 1 and request[0] == "__QUERY_MODEL__":
|
if len(request) == 1 and request[0] == "__QUERY_MODEL__":
|
||||||
response_bytes = msgpack.packb([model_name])
|
response_bytes = msgpack.packb([model_name])
|
||||||
socket.send(response_bytes)
|
rep_socket.send(response_bytes)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
node_ids = request
|
# Handle direct text embedding request
|
||||||
|
if (
|
||||||
|
isinstance(request, list)
|
||||||
|
and request
|
||||||
|
and all(isinstance(item, str) for item in request)
|
||||||
|
):
|
||||||
|
embeddings = compute_embeddings(request, model_name, mode=embedding_mode)
|
||||||
|
rep_socket.send(msgpack.packb(embeddings.tolist()))
|
||||||
|
e2e_end = time.time()
|
||||||
|
logger.info(f"⏱️ Text embedding E2E time: {e2e_end - e2e_start:.6f}s")
|
||||||
|
continue
|
||||||
|
|
||||||
|
node_ids = request if isinstance(request, list) else []
|
||||||
logger.info(f"ZMQ received {len(node_ids)} node IDs")
|
logger.info(f"ZMQ received {len(node_ids)} node IDs")
|
||||||
|
|
||||||
texts = []
|
texts = []
|
||||||
for nid in node_ids:
|
for nid in node_ids:
|
||||||
try:
|
try:
|
||||||
txt = passages.get_text(nid)
|
passage_data = passages.get_passage(str(nid))
|
||||||
|
txt = passage_data["text"]
|
||||||
if not txt:
|
if not txt:
|
||||||
raise RuntimeError(f"FATAL: Empty text for passage ID {nid}")
|
raise RuntimeError(f"FATAL: Empty text for passage ID {nid}")
|
||||||
texts.append(txt)
|
texts.append(txt)
|
||||||
@@ -295,7 +317,7 @@ def create_hnsw_embedding_server(
|
|||||||
]
|
]
|
||||||
response_bytes = msgpack.packb(response_payload, use_single_float=True)
|
response_bytes = msgpack.packb(response_payload, use_single_float=True)
|
||||||
|
|
||||||
socket.send(response_bytes)
|
rep_socket.send(response_bytes)
|
||||||
e2e_end = time.time()
|
e2e_end = time.time()
|
||||||
logger.info(f"⏱️ ZMQ E2E time: {e2e_end - e2e_start:.6f}s")
|
logger.info(f"⏱️ ZMQ E2E time: {e2e_end - e2e_start:.6f}s")
|
||||||
|
|
||||||
@@ -306,12 +328,21 @@ def create_hnsw_embedding_server(
|
|||||||
if not shutdown_event.is_set():
|
if not shutdown_event.is_set():
|
||||||
logger.error(f"Error in ZMQ server loop: {e}")
|
logger.error(f"Error in ZMQ server loop: {e}")
|
||||||
try:
|
try:
|
||||||
socket.send(msgpack.packb([[], []]))
|
rep_socket.send(msgpack.packb([[], []]))
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
logger.info("Shutdown in progress, ignoring ZMQ error")
|
logger.info("Shutdown in progress, ignoring ZMQ error")
|
||||||
break
|
break
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
rep_socket.close(0)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
context.term()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
logger.info("ZMQ server thread exiting gracefully")
|
logger.info("ZMQ server thread exiting gracefully")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user