-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #99 from neptune-ai/ms/error-tracking-retryable-error
fix: Retry on NeptuneRetryableError. Call on_async_lag_callback
- Loading branch information
Showing
4 changed files
with
212 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
import queue | ||
import time | ||
from typing import List | ||
from unittest.mock import Mock | ||
|
||
import pytest | ||
from neptune_api.proto.neptune_pb.ingest.v1.common_pb2 import ( | ||
UpdateRunSnapshot, | ||
Value, | ||
) | ||
from neptune_api.proto.neptune_pb.ingest.v1.pub.client_pb2 import SubmitResponse | ||
from neptune_api.proto.neptune_pb.ingest.v1.pub.ingest_pb2 import RunOperation | ||
|
||
from neptune_scale.exceptions import NeptuneSynchronizationStopped | ||
from neptune_scale.sync.queue_element import ( | ||
BatchedOperations, | ||
SingleOperation, | ||
) | ||
from neptune_scale.sync.sync_process import SenderThread | ||
from neptune_scale.util.shared_var import SharedInt | ||
|
||
|
||
def response(request_ids: List[str], status_code: int = 200): | ||
body = SubmitResponse(request_ids=request_ids, request_id=request_ids[-1] if request_ids else None) | ||
content = body.SerializeToString() | ||
return Mock(status_code=status_code, content=content, parsed=body) | ||
|
||
|
||
def single_operation(update: UpdateRunSnapshot, sequence_id): | ||
operation = RunOperation(update=update) | ||
return SingleOperation( | ||
sequence_id=sequence_id, | ||
timestamp=time.process_time(), | ||
operation=operation.SerializeToString(), | ||
is_batchable=True, | ||
metadata_size=update.ByteSize(), | ||
batch_key=None, | ||
) | ||
|
||
|
||
def test_sender_thread_work_finishes_when_queue_empty(): | ||
# given | ||
operations_queue = Mock() | ||
status_tracking_queue = Mock() | ||
errors_queue = Mock() | ||
last_queue_seq = SharedInt(initial_value=0) | ||
backend = Mock() | ||
sender_thread = SenderThread( | ||
api_token="", | ||
family="", | ||
operations_queue=operations_queue, | ||
status_tracking_queue=status_tracking_queue, | ||
errors_queue=errors_queue, | ||
last_queued_seq=last_queue_seq, | ||
mode="disabled", | ||
) | ||
sender_thread._backend = backend | ||
|
||
# and | ||
operations_queue.get.side_effect = queue.Empty | ||
|
||
# when | ||
sender_thread.work() | ||
|
||
# then | ||
assert True | ||
|
||
|
||
def test_sender_thread_processes_single_element(): | ||
# given | ||
operations_queue = Mock() | ||
status_tracking_queue = Mock() | ||
errors_queue = Mock() | ||
last_queue_seq = SharedInt(initial_value=0) | ||
backend = Mock() | ||
sender_thread = SenderThread( | ||
api_token="", | ||
family="", | ||
operations_queue=operations_queue, | ||
status_tracking_queue=status_tracking_queue, | ||
errors_queue=errors_queue, | ||
last_queued_seq=last_queue_seq, | ||
mode="disabled", | ||
) | ||
sender_thread._backend = backend | ||
|
||
# and | ||
update = UpdateRunSnapshot(assign={"key": Value(string="a")}) | ||
element = single_operation(update, sequence_id=2) | ||
operations_queue.get.side_effect = [ | ||
BatchedOperations(sequence_id=element.sequence_id, timestamp=element.timestamp, operation=element.operation), | ||
queue.Empty, | ||
] | ||
|
||
# and | ||
backend.submit.side_effect = [response(["1"])] | ||
|
||
# when | ||
sender_thread.work() | ||
|
||
# then | ||
assert backend.submit.call_count == 1 | ||
|
||
|
||
def test_sender_thread_processes_element_on_single_retryable_error(): | ||
# given | ||
operations_queue = Mock() | ||
status_tracking_queue = Mock() | ||
errors_queue = Mock() | ||
last_queue_seq = SharedInt(initial_value=0) | ||
backend = Mock() | ||
sender_thread = SenderThread( | ||
api_token="", | ||
family="", | ||
operations_queue=operations_queue, | ||
status_tracking_queue=status_tracking_queue, | ||
errors_queue=errors_queue, | ||
last_queued_seq=last_queue_seq, | ||
mode="disabled", | ||
) | ||
sender_thread._backend = backend | ||
|
||
# and | ||
update = UpdateRunSnapshot(assign={"key": Value(string="a")}) | ||
element = single_operation(update, sequence_id=2) | ||
operations_queue.get.side_effect = [ | ||
BatchedOperations(sequence_id=element.sequence_id, timestamp=element.timestamp, operation=element.operation), | ||
queue.Empty, | ||
] | ||
|
||
# and | ||
backend.submit.side_effect = [ | ||
response([], status_code=503), | ||
response(["a"], status_code=200), | ||
] | ||
|
||
# when | ||
sender_thread.work() | ||
|
||
# then | ||
assert backend.submit.call_count == 2 | ||
|
||
|
||
def test_sender_thread_fails_on_regular_error(): | ||
# given | ||
operations_queue = Mock() | ||
status_tracking_queue = Mock() | ||
errors_queue = Mock() | ||
last_queue_seq = SharedInt(initial_value=0) | ||
backend = Mock() | ||
sender_thread = SenderThread( | ||
api_token="", | ||
family="", | ||
operations_queue=operations_queue, | ||
status_tracking_queue=status_tracking_queue, | ||
errors_queue=errors_queue, | ||
last_queued_seq=last_queue_seq, | ||
mode="disabled", | ||
) | ||
sender_thread._backend = backend | ||
|
||
# and | ||
update = UpdateRunSnapshot(assign={"key": Value(string="a")}) | ||
element = single_operation(update, sequence_id=2) | ||
operations_queue.get.side_effect = [ | ||
BatchedOperations(sequence_id=element.sequence_id, timestamp=element.timestamp, operation=element.operation), | ||
queue.Empty, | ||
] | ||
|
||
# and | ||
backend.submit.side_effect = [ | ||
response([], status_code=200), | ||
] | ||
|
||
# when | ||
with pytest.raises(NeptuneSynchronizationStopped): | ||
sender_thread.work() | ||
|
||
# then should throw NeptuneInternalServerError | ||
errors_queue.put.assert_called_once() |