-
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.
- Loading branch information
1 parent
022ce35
commit 4909b26
Showing
8 changed files
with
259 additions
and
72 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
from __future__ import annotations | ||
|
||
import multiprocessing | ||
import queue | ||
from typing import Optional | ||
|
||
from neptune_api.proto.neptune_pb.ingest.v1.pub.ingest_pb2 import RunOperation | ||
|
||
from neptune_scale.exceptions import ( | ||
NeptuneOperationsQueueMaxSizeExceeded, | ||
NeptuneSynchronizationStopped, | ||
) | ||
from neptune_scale.sync.aggregating_queue import AggregatingQueue | ||
from neptune_scale.sync.errors_tracking import ErrorsQueue | ||
from neptune_scale.sync.file_upload import FileUploader | ||
from neptune_scale.sync.parameters import INTERNAL_QUEUE_FEEDER_THREAD_SLEEP_TIME | ||
from neptune_scale.sync.queue_element import ( | ||
OperationMessage, | ||
OperationType, | ||
) | ||
from neptune_scale.util import ( | ||
Daemon, | ||
logger, | ||
) | ||
from neptune_scale.util.abstract import Resource | ||
|
||
|
||
class OperationDispatcherThread(Daemon, Resource): | ||
"""Retrieves messages from the operations queue that is fed by the main process. Dispatches messages based on | ||
their type: | ||
* SingleOperation: common logging operations - push to the aggregating queue | ||
* UploadFileOperation: push to file upload queue | ||
""" | ||
|
||
def __init__( | ||
self, | ||
input_queue: multiprocessing.Queue[OperationMessage], | ||
operations_queue: AggregatingQueue, | ||
errors_queue: ErrorsQueue, | ||
file_uploader: FileUploader, | ||
) -> None: | ||
super().__init__(name="OperationDispatcherThread", sleep_time=INTERNAL_QUEUE_FEEDER_THREAD_SLEEP_TIME) | ||
|
||
self._input_queue: multiprocessing.Queue[OperationMessage] = input_queue | ||
self._operations_queue: AggregatingQueue = operations_queue | ||
self._errors_queue: ErrorsQueue = errors_queue | ||
self._file_uploader: FileUploader = file_uploader | ||
|
||
self._latest_unprocessed: Optional[OperationMessage] = None | ||
|
||
def get_next(self) -> Optional[OperationMessage]: | ||
if self._latest_unprocessed is not None: | ||
return self._latest_unprocessed | ||
|
||
try: | ||
self._latest_unprocessed = self._input_queue.get(timeout=INTERNAL_QUEUE_FEEDER_THREAD_SLEEP_TIME) | ||
return self._latest_unprocessed | ||
except queue.Empty: | ||
return None | ||
|
||
def commit(self) -> None: | ||
self._latest_unprocessed = None | ||
|
||
def work(self) -> None: | ||
try: | ||
while not self._is_interrupted(): | ||
message = self.get_next() | ||
if message is None: | ||
continue | ||
|
||
if not self.dispatch(message): | ||
break | ||
except Exception as e: | ||
self._errors_queue.put(e) | ||
self.interrupt() | ||
raise NeptuneSynchronizationStopped() from e | ||
|
||
def dispatch(self, message: OperationMessage) -> bool: | ||
op = message.operation | ||
try: | ||
if message.type == OperationType.SINGLE_OPERATION: | ||
self._operations_queue.put_nowait(op) | ||
elif message.type == OperationType.UPLOAD_FILE: | ||
self._file_uploader.start_upload( | ||
self._finalize_file_upload, op.local_path, op.target_path, op.target_basename | ||
) | ||
|
||
self.commit() | ||
return True | ||
except queue.Full: | ||
logger.debug( | ||
"Operations queue is full (%d elements), waiting for free space", self._operations_queue.maxsize | ||
) | ||
self._errors_queue.put(NeptuneOperationsQueueMaxSizeExceeded(max_size=self._operations_queue.maxsize)) | ||
return False | ||
|
||
def _finalize_file_upload(self, path: str, error: Optional[Exception]) -> None: | ||
if error: | ||
self._errors_queue.put(error) | ||
return | ||
|
||
op = RunOperation() | ||
# TODO: Fill it out once we have established the protocol with the backend | ||
self._operations_queue.put_nowait(op) |
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,77 @@ | ||
from concurrent import futures | ||
from pathlib import Path | ||
from typing import ( | ||
Optional, | ||
Protocol, | ||
) | ||
|
||
from neptune_scale.sync.errors_tracking import ErrorsQueue | ||
from neptune_scale.util import SharedInt | ||
|
||
|
||
class Finalizer(Protocol): | ||
def __call__(self, path: str, error: Optional[Exception] = None) -> None: ... | ||
|
||
|
||
class FileUploader: | ||
def __init__( | ||
self, project: str, api_token: str, family: str, in_progress_counter: SharedInt, errors_queue: ErrorsQueue | ||
) -> None: | ||
self._project = project | ||
self._api_token = api_token | ||
self._family = family | ||
self._errors_queue = errors_queue | ||
self._in_progress_counter = in_progress_counter | ||
self._executor = futures.ThreadPoolExecutor() | ||
|
||
def start_upload( | ||
self, finalizer: Finalizer, local_path: Path, target_path: Optional[str], target_basename: Optional[str] | ||
) -> None: | ||
with self._in_progress_counter: | ||
self._in_progress_counter.value += 1 | ||
|
||
self._executor.submit(self._do_upload, finalizer, local_path, target_path, target_basename) | ||
|
||
def _do_upload( | ||
self, finalizer: Finalizer, local_path: Path, target_path: Optional[str], target_basename: Optional[str] | ||
) -> None: | ||
path = determine_path(local_path, target_path, target_basename) | ||
|
||
try: | ||
url = self._request_upload_url(path) | ||
upload_file(local_path, url) | ||
error = None | ||
except Exception as e: | ||
error = e | ||
|
||
finalizer(path, error=error) | ||
|
||
with self._in_progress_counter: | ||
self._in_progress_counter.value -= 1 | ||
assert self._in_progress_counter.value >= 0 | ||
|
||
self._in_progress_counter.notify_all() | ||
|
||
def _request_upload_url(self, path) -> str: | ||
assert self._api_token | ||
# TODO: temporary | ||
return "http://localhost:8012/" + path | ||
|
||
def wait_for_completion(self) -> None: | ||
self._executor.shutdown() | ||
with self._in_progress_counter: | ||
assert self._in_progress_counter.value >= 0 | ||
|
||
|
||
def determine_path(local_path: Path, target_path: Optional[str], target_basename: Optional[str]) -> str: | ||
if target_path: | ||
return target_path | ||
|
||
# TODO: figure out the path | ||
return str(Path("DUMMY_PATH") / local_path) | ||
|
||
|
||
def upload_file(local_path: Path, url: str) -> None: | ||
# TODO: do the actual work :) | ||
assert local_path and url | ||
pass |
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
Oops, something went wrong.