indexify 0.2.48__py3-none-any.whl → 0.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. indexify/{cli.py → cli/cli.py} +75 -82
  2. indexify/executor/README.md +35 -0
  3. indexify/executor/api_objects.py +9 -3
  4. indexify/executor/downloader.py +5 -5
  5. indexify/executor/executor.py +35 -22
  6. indexify/executor/function_executor/function_executor.py +14 -3
  7. indexify/executor/function_executor/function_executor_state.py +13 -10
  8. indexify/executor/function_executor/invocation_state_client.py +2 -1
  9. indexify/executor/function_executor/server/subprocess_function_executor_server_factory.py +22 -10
  10. indexify/executor/function_executor/single_task_runner.py +43 -26
  11. indexify/executor/function_executor/task_input.py +1 -3
  12. indexify/executor/task_fetcher.py +5 -7
  13. indexify/executor/task_reporter.py +3 -5
  14. indexify/executor/task_runner.py +31 -24
  15. indexify/function_executor/README.md +18 -0
  16. indexify/function_executor/handlers/run_function/function_inputs_loader.py +13 -14
  17. indexify/function_executor/handlers/run_function/handler.py +16 -40
  18. indexify/function_executor/handlers/run_function/request_validator.py +7 -5
  19. indexify/function_executor/handlers/run_function/response_helper.py +6 -8
  20. indexify/function_executor/initialize_request_validator.py +1 -2
  21. indexify/function_executor/invocation_state/invocation_state_proxy_server.py +1 -1
  22. indexify/function_executor/invocation_state/proxied_invocation_state.py +1 -3
  23. indexify/function_executor/main.py +50 -0
  24. indexify/function_executor/proto/configuration.py +8 -0
  25. indexify/function_executor/proto/function_executor.proto +9 -4
  26. indexify/function_executor/proto/function_executor_pb2.py +24 -24
  27. indexify/function_executor/proto/function_executor_pb2.pyi +24 -4
  28. indexify/function_executor/server.py +4 -6
  29. indexify/function_executor/{function_executor_service.py → service.py} +35 -24
  30. indexify/utils/README.md +3 -0
  31. indexify/{common_util.py → utils/http_client.py} +2 -2
  32. indexify/{logging.py → utils/logging.py} +36 -2
  33. indexify-0.3.1.dist-info/METADATA +38 -0
  34. indexify-0.3.1.dist-info/RECORD +44 -0
  35. {indexify-0.2.48.dist-info → indexify-0.3.1.dist-info}/WHEEL +1 -1
  36. indexify-0.3.1.dist-info/entry_points.txt +4 -0
  37. indexify/__init__.py +0 -31
  38. indexify/data_loaders/__init__.py +0 -58
  39. indexify/data_loaders/local_directory_loader.py +0 -37
  40. indexify/data_loaders/url_loader.py +0 -52
  41. indexify/error.py +0 -8
  42. indexify/functions_sdk/data_objects.py +0 -27
  43. indexify/functions_sdk/graph.py +0 -364
  44. indexify/functions_sdk/graph_definition.py +0 -63
  45. indexify/functions_sdk/graph_validation.py +0 -70
  46. indexify/functions_sdk/image.py +0 -222
  47. indexify/functions_sdk/indexify_functions.py +0 -354
  48. indexify/functions_sdk/invocation_state/invocation_state.py +0 -22
  49. indexify/functions_sdk/invocation_state/local_invocation_state.py +0 -30
  50. indexify/functions_sdk/object_serializer.py +0 -68
  51. indexify/functions_sdk/pipeline.py +0 -33
  52. indexify/http_client.py +0 -379
  53. indexify/remote_graph.py +0 -138
  54. indexify/remote_pipeline.py +0 -25
  55. indexify/settings.py +0 -1
  56. indexify-0.2.48.dist-info/LICENSE.txt +0 -201
  57. indexify-0.2.48.dist-info/METADATA +0 -154
  58. indexify-0.2.48.dist-info/RECORD +0 -60
  59. indexify-0.2.48.dist-info/entry_points.txt +0 -3
@@ -12,7 +12,7 @@ from indexify.function_executor.proto.function_executor_pb2_grpc import (
12
12
  )
13
13
 
14
14
  from ..api_objects import Task
15
- from .function_executor import FunctionExecutor
15
+ from .function_executor import CustomerError, FunctionExecutor
16
16
  from .function_executor_state import FunctionExecutorState
17
17
  from .server.function_executor_server_factory import (
18
18
  FunctionExecutorServerConfiguration,
@@ -49,8 +49,18 @@ class SingleTaskRunner:
49
49
  Raises an exception if an error occured."""
50
50
  self._state.check_locked()
51
51
 
52
+ if self._state.is_shutdown:
53
+ raise RuntimeError("Function Executor state is shutting down.")
54
+
52
55
  if self._state.function_executor is None:
53
- self._state.function_executor = await self._create_function_executor()
56
+ try:
57
+ await self._create_function_executor()
58
+ except CustomerError as e:
59
+ return TaskOutput(
60
+ task=self._task_input.task,
61
+ stderr=str(e),
62
+ success=False,
63
+ )
54
64
 
55
65
  return await self._run()
56
66
 
@@ -58,36 +68,37 @@ class SingleTaskRunner:
58
68
  function_executor: FunctionExecutor = FunctionExecutor(
59
69
  server_factory=self._factory, logger=self._logger
60
70
  )
61
- try:
62
- config: FunctionExecutorServerConfiguration = (
63
- FunctionExecutorServerConfiguration(
64
- image_uri=self._task_input.task.image_uri,
65
- )
66
- )
67
- initialize_request: InitializeRequest = InitializeRequest(
68
- namespace=self._task_input.task.namespace,
69
- graph_name=self._task_input.task.compute_graph,
70
- graph_version=self._task_input.task.graph_version,
71
- function_name=self._task_input.task.compute_fn,
72
- graph=self._task_input.graph,
71
+ config: FunctionExecutorServerConfiguration = (
72
+ FunctionExecutorServerConfiguration(
73
+ image_uri=self._task_input.task.image_uri,
73
74
  )
75
+ )
76
+ initialize_request: InitializeRequest = InitializeRequest(
77
+ namespace=self._task_input.task.namespace,
78
+ graph_name=self._task_input.task.compute_graph,
79
+ graph_version=self._task_input.task.graph_version,
80
+ function_name=self._task_input.task.compute_fn,
81
+ graph=self._task_input.graph,
82
+ )
83
+
84
+ try:
74
85
  await function_executor.initialize(
75
86
  config=config,
76
87
  initialize_request=initialize_request,
77
88
  base_url=self._base_url,
78
89
  config_path=self._config_path,
79
90
  )
80
- return function_executor
81
- except Exception as e:
82
- self._logger.error(
83
- f"failed to initialize function executor: {e.details()}",
84
- # exc_info=e.details(),
85
- )
91
+ self._state.function_executor = function_executor
92
+ except Exception:
86
93
  await function_executor.destroy()
87
94
  raise
88
95
 
89
96
  async def _run(self) -> TaskOutput:
90
97
  request: RunTaskRequest = RunTaskRequest(
98
+ namespace=self._task_input.task.namespace,
99
+ graph_name=self._task_input.task.compute_graph,
100
+ graph_version=self._task_input.task.graph_version,
101
+ function_name=self._task_input.task.compute_fn,
91
102
  graph_invocation_id=self._task_input.task.invocation_id,
92
103
  task_id=self._task_input.task.id,
93
104
  function_input=self._task_input.input,
@@ -97,7 +108,9 @@ class SingleTaskRunner:
97
108
  channel: grpc.aio.Channel = self._state.function_executor.channel()
98
109
 
99
110
  async with _RunningTaskContextManager(
100
- task_input=self._task_input, function_executor_state=self._state
111
+ invocation_id=self._task_input.task.invocation_id,
112
+ task_id=self._task_input.task.id,
113
+ function_executor_state=self._state,
101
114
  ):
102
115
  response: RunTaskResponse = await FunctionExecutorStub(channel).run_task(
103
116
  request
@@ -109,16 +122,20 @@ class _RunningTaskContextManager:
109
122
  """Performs all the actions required before and after running a task."""
110
123
 
111
124
  def __init__(
112
- self, task_input: TaskInput, function_executor_state: FunctionExecutorState
125
+ self,
126
+ invocation_id: str,
127
+ task_id: str,
128
+ function_executor_state: FunctionExecutorState,
113
129
  ):
114
- self._task_input: TaskInput = task_input
130
+ self._invocation_id: str = invocation_id
131
+ self._task_id: str = task_id
115
132
  self._state: FunctionExecutorState = function_executor_state
116
133
 
117
134
  async def __aenter__(self):
118
135
  self._state.increment_running_tasks()
119
136
  self._state.function_executor.invocation_state_client().add_task_to_invocation_id_entry(
120
- task_id=self._task_input.task.id,
121
- invocation_id=self._task_input.task.invocation_id,
137
+ task_id=self._task_id,
138
+ invocation_id=self._invocation_id,
122
139
  )
123
140
  # Unlock the state so other tasks can act depending on it.
124
141
  self._state.lock.release()
@@ -128,7 +145,7 @@ class _RunningTaskContextManager:
128
145
  await self._state.lock.acquire()
129
146
  self._state.decrement_running_tasks()
130
147
  self._state.function_executor.invocation_state_client().remove_task_to_invocation_id_entry(
131
- task_id=self._task_input.task.id
148
+ task_id=self._task_id
132
149
  )
133
150
 
134
151
 
@@ -1,8 +1,6 @@
1
1
  from typing import Optional
2
2
 
3
- from indexify.function_executor.proto.function_executor_pb2 import (
4
- SerializedObject,
5
- )
3
+ from indexify.function_executor.proto.function_executor_pb2 import SerializedObject
6
4
 
7
5
  from ..api_objects import Task
8
6
 
@@ -1,13 +1,13 @@
1
1
  import json
2
2
  from importlib.metadata import version
3
- from typing import AsyncGenerator, Optional
3
+ from typing import AsyncGenerator, List, Optional
4
4
 
5
5
  import structlog
6
6
  from httpx_sse import aconnect_sse
7
7
 
8
- from indexify.common_util import get_httpx_client
8
+ from indexify.utils.http_client import get_httpx_client
9
9
 
10
- from .api_objects import ExecutorMetadata, Task
10
+ from .api_objects import ExecutorMetadata, FunctionURI, Task
11
11
  from .runtime_probes import ProbeInfo, RuntimeProbes
12
12
 
13
13
 
@@ -19,8 +19,7 @@ class TaskFetcher:
19
19
  protocol: str,
20
20
  indexify_server_addr: str,
21
21
  executor_id: str,
22
- name_alias: Optional[str] = None,
23
- image_hash: Optional[int] = None,
22
+ function_allowlist: Optional[List[FunctionURI]],
24
23
  config_path: Optional[str] = None,
25
24
  ):
26
25
  self._protocol: str = protocol
@@ -33,8 +32,7 @@ class TaskFetcher:
33
32
  id=executor_id,
34
33
  executor_version=version("indexify"),
35
34
  addr="",
36
- image_name=probe_info.image_name if name_alias is None else name_alias,
37
- image_hash=(probe_info.image_hash if image_hash is None else image_hash),
35
+ function_allowlist=function_allowlist,
38
36
  labels=probe_info.labels,
39
37
  )
40
38
 
@@ -4,12 +4,10 @@ from typing import Any, List, Optional, Tuple
4
4
  import nanoid
5
5
  from httpx import Timeout
6
6
 
7
- from indexify.common_util import get_httpx_client
8
- from indexify.executor.api_objects import RouterOutput, Task, TaskResult
9
- from indexify.function_executor.proto.function_executor_pb2 import (
10
- FunctionOutput,
11
- )
7
+ from indexify.function_executor.proto.function_executor_pb2 import FunctionOutput
8
+ from indexify.utils.http_client import get_httpx_client
12
9
 
10
+ from .api_objects import RouterOutput, Task, TaskResult
13
11
  from .task_runner import TaskOutput
14
12
 
15
13
 
@@ -1,3 +1,4 @@
1
+ import asyncio
1
2
  from typing import Any, Dict, Optional
2
3
 
3
4
  from .api_objects import Task
@@ -24,7 +25,9 @@ class TaskRunner:
24
25
  self._factory: FunctionExecutorServerFactory = function_executor_server_factory
25
26
  self._base_url: str = base_url
26
27
  self._config_path: Optional[str] = config_path
27
- # We don't lock this map cause we never await while reading and modifying it.
28
+ # The fields below are protected by the lock.
29
+ self._lock: asyncio.Lock = asyncio.Lock()
30
+ self._is_shutdown: bool = False
28
31
  self._function_executor_states: Dict[str, FunctionExecutorState] = {}
29
32
 
30
33
  async def run(self, task_input: TaskInput, logger: Any) -> TaskOutput:
@@ -33,17 +36,31 @@ class TaskRunner:
33
36
  return await self._run(task_input, logger)
34
37
  except Exception as e:
35
38
  logger.error(
36
- f"failed running the task: {e.details()}",
37
- # exc_info=e.debug_error_string(),
39
+ "failed running the task:",
40
+ exc_info=e,
38
41
  )
39
42
  return TaskOutput.internal_error(task_input.task)
40
43
 
41
44
  async def _run(self, task_input: TaskInput, logger: Any) -> TaskOutput:
42
- state = self._get_or_create_state(task_input.task)
45
+ state = await self._get_or_create_state(task_input.task)
43
46
  async with state.lock:
44
47
  await self._run_task_policy(state, task_input.task)
45
48
  return await self._run_task(state, task_input, logger)
46
49
 
50
+ async def _get_or_create_state(self, task: Task) -> FunctionExecutorState:
51
+ async with self._lock:
52
+ if self._is_shutdown:
53
+ raise RuntimeError("Task runner is shutting down.")
54
+
55
+ id = _function_id_without_version(task)
56
+ if id not in self._function_executor_states:
57
+ state = FunctionExecutorState(
58
+ function_id_with_version=_function_id_with_version(task),
59
+ function_id_without_version=id,
60
+ )
61
+ self._function_executor_states[id] = state
62
+ return self._function_executor_states[id]
63
+
47
64
  async def _run_task_policy(self, state: FunctionExecutorState, task: Task) -> None:
48
65
  # Current policy for running tasks:
49
66
  # - There can only be a single Function Executor per function regardless of function versions.
@@ -60,16 +77,6 @@ class TaskRunner:
60
77
  # At this point the state belongs to the version of the function from the task
61
78
  # and there are no running tasks in the Function Executor.
62
79
 
63
- def _get_or_create_state(self, task: Task) -> FunctionExecutorState:
64
- id = _function_id_without_version(task)
65
- if id not in self._function_executor_states:
66
- state = FunctionExecutorState(
67
- function_id_with_version=_function_id_with_version(task),
68
- function_id_without_version=id,
69
- )
70
- self._function_executor_states[id] = state
71
- return self._function_executor_states[id]
72
-
73
80
  async def _run_task(
74
81
  self, state: FunctionExecutorState, task_input: TaskInput, logger: Any
75
82
  ) -> TaskOutput:
@@ -84,16 +91,16 @@ class TaskRunner:
84
91
  return await runner.run()
85
92
 
86
93
  async def shutdown(self) -> None:
87
- # When shutting down there's no need to wait for completion of the running
88
- # FunctionExecutor tasks.
89
- while self._function_executor_states:
90
- id, state = self._function_executor_states.popitem()
91
- # At this point the state is not visible to new tasks.
92
- # Only ongoing tasks who read it already have a reference to it.
93
- await state.destroy_function_executor_not_locked()
94
- # The task running inside the Function Executor will fail because it's destroyed.
95
- # asyncio tasks waiting to run inside the Function Executor will get cancelled by
96
- # the caller's shutdown code.
94
+ # Function Executors are outside the Executor process
95
+ # so they need to get cleaned up explicitly and reliably.
96
+ async with self._lock:
97
+ self._is_shutdown = True # No new Function Executor States can be created.
98
+ while self._function_executor_states:
99
+ id, state = self._function_executor_states.popitem()
100
+ # Only ongoing tasks who have a reference to the state already can see it.
101
+ async with state.lock:
102
+ await state.shutdown()
103
+ # The task running inside the Function Executor will fail because it's destroyed.
97
104
 
98
105
 
99
106
  def _function_id_with_version(task: Task) -> str:
@@ -0,0 +1,18 @@
1
+ ## Overview
2
+
3
+ Function Executor is a process with an API that allows to load and run a customer Function in Indexify.
4
+ Each function run is a task. The tasks can be executed concurrently. The API client controls
5
+ the desired concurrency. Killing the process allows to free all the resources that a loaded customer
6
+ functon is using. This is helpful because the SDK doesn't provide any callbacks to customer code to free
7
+ resources it's using. Even if there was such callback customer code still might misbehave.
8
+
9
+ ## Deployment
10
+
11
+ A Function Executor is created and destroyed by another component called Executor. It also calls the
12
+ Function Executor APIs. The server is not expected to be deployed or managed manually by Indexify users
13
+ as it's a low level component.
14
+
15
+ ## Threat model
16
+
17
+ Customer code is assumed to be not trusted. Function Executor must not obtain any credentials that grant
18
+ access to resources not owned by the customer who owns the function.
@@ -1,18 +1,17 @@
1
1
  from typing import Optional
2
2
 
3
- from pydantic import BaseModel
3
+ from tensorlake.functions_sdk.data_objects import TensorlakeData
4
+ from tensorlake.functions_sdk.object_serializer import get_serializer
4
5
 
5
- from indexify.function_executor.proto.function_executor_pb2 import (
6
- RunTaskRequest,
7
- SerializedObject,
8
- )
9
- from indexify.functions_sdk.data_objects import IndexifyData
10
- from indexify.functions_sdk.object_serializer import get_serializer
6
+ from ...proto.function_executor_pb2 import RunTaskRequest, SerializedObject
11
7
 
12
8
 
13
- class FunctionInputs(BaseModel):
14
- input: IndexifyData
15
- init_value: Optional[IndexifyData] = None
9
+ class FunctionInputs:
10
+ def __init__(
11
+ self, input: TensorlakeData, init_value: Optional[TensorlakeData] = None
12
+ ):
13
+ self.input = input
14
+ self.init_value = init_value
16
15
 
17
16
 
18
17
  class FunctionInputsLoader:
@@ -25,12 +24,12 @@ class FunctionInputsLoader:
25
24
  init_value=self._accumulator_input(),
26
25
  )
27
26
 
28
- def _function_input(self) -> IndexifyData:
27
+ def _function_input(self) -> TensorlakeData:
29
28
  return _to_indexify_data(
30
29
  self._request.graph_invocation_id, self._request.function_input
31
30
  )
32
31
 
33
- def _accumulator_input(self) -> Optional[IndexifyData]:
32
+ def _accumulator_input(self) -> Optional[TensorlakeData]:
34
33
  return (
35
34
  _to_indexify_data(
36
35
  self._request.graph_invocation_id, self._request.function_init_value
@@ -42,8 +41,8 @@ class FunctionInputsLoader:
42
41
 
43
42
  def _to_indexify_data(
44
43
  input_id: str, serialized_object: SerializedObject
45
- ) -> IndexifyData:
46
- return IndexifyData(
44
+ ) -> TensorlakeData:
45
+ return TensorlakeData(
47
46
  input_id=input_id,
48
47
  payload=(
49
48
  serialized_object.bytes
@@ -2,25 +2,19 @@ import io
2
2
  import sys
3
3
  import traceback
4
4
  from contextlib import redirect_stderr, redirect_stdout
5
- from typing import Any, Optional, Union
5
+ from typing import Any, Union
6
6
 
7
- from indexify.function_executor.proto.function_executor_pb2 import (
8
- RunTaskRequest,
9
- RunTaskResponse,
10
- )
11
- from indexify.functions_sdk.indexify_functions import (
7
+ from tensorlake.functions_sdk.functions import (
12
8
  FunctionCallResult,
13
9
  GraphInvocationContext,
14
- IndexifyFunction,
15
- IndexifyFunctionWrapper,
16
- IndexifyRouter,
17
10
  RouterCallResult,
11
+ TensorlakeCompute,
12
+ TensorlakeFunctionWrapper,
13
+ TensorlakeRouter,
18
14
  )
19
- from indexify.functions_sdk.invocation_state.invocation_state import (
20
- InvocationState,
21
- )
22
- from indexify.http_client import IndexifyClient
15
+ from tensorlake.functions_sdk.invocation_state.invocation_state import InvocationState
23
16
 
17
+ from ...proto.function_executor_pb2 import RunTaskRequest, RunTaskResponse
24
18
  from .function_inputs_loader import FunctionInputs, FunctionInputsLoader
25
19
  from .response_helper import ResponseHelper
26
20
 
@@ -30,9 +24,9 @@ class Handler:
30
24
  self,
31
25
  request: RunTaskRequest,
32
26
  graph_name: str,
33
- graph_version: int,
27
+ graph_version: str,
34
28
  function_name: str,
35
- function: Union[IndexifyFunction, IndexifyRouter],
29
+ function: Union[TensorlakeCompute, TensorlakeCompute],
36
30
  invocation_state: InvocationState,
37
31
  logger: Any,
38
32
  ):
@@ -48,12 +42,12 @@ class Handler:
48
42
  self._func_stdout: io.StringIO = io.StringIO()
49
43
  self._func_stderr: io.StringIO = io.StringIO()
50
44
 
51
- self._function_wrapper: IndexifyFunctionWrapper = IndexifyFunctionWrapper(
45
+ self._function_wrapper: TensorlakeFunctionWrapper = TensorlakeFunctionWrapper(
52
46
  indexify_function=function,
53
47
  context=GraphInvocationContext(
54
48
  invocation_id=request.graph_invocation_id,
55
49
  graph_name=graph_name,
56
- graph_version=str(graph_version),
50
+ graph_version=graph_version,
57
51
  invocation_state=invocation_state,
58
52
  ),
59
53
  )
@@ -116,35 +110,17 @@ class Handler:
116
110
  sys.stderr.flush()
117
111
 
118
112
 
119
- def _indexify_client(
120
- logger: Any,
121
- namespace: str,
122
- indexify_server_addr: str,
123
- config_path: Optional[str],
124
- ) -> IndexifyClient:
125
- # This client is required to implement key/value store functionality for customer functions.
126
- protocol: str = "http"
127
- if config_path:
128
- logger.info("TLS is enabled")
129
- protocol = "https"
130
- return IndexifyClient(
131
- service_url=f"{protocol}://{indexify_server_addr}",
132
- namespace=namespace,
133
- config_path=config_path,
134
- )
135
-
136
-
137
- def _is_router(func_wrapper: IndexifyFunctionWrapper) -> bool:
113
+ def _is_router(func_wrapper: TensorlakeFunctionWrapper) -> bool:
138
114
  """Determines if the function is a router.
139
115
 
140
- A function is a router if it is an instance of IndexifyRouter or if it is an IndexifyRouter class.
116
+ A function is a router if it is an instance of TensorlakeRouter or if it is an TensorlakeRouter class.
141
117
  """
142
118
  return str(
143
119
  type(func_wrapper.indexify_function)
144
- ) == "<class 'indexify.functions_sdk.indexify_functions.IndexifyRouter'>" or isinstance(
145
- func_wrapper.indexify_function, IndexifyRouter
120
+ ) == "<class 'tensorlake.functions_sdk.functions.TensorlakeRouter'>" or isinstance(
121
+ func_wrapper.indexify_function, TensorlakeRouter
146
122
  )
147
123
 
148
124
 
149
- def _func_is_reducer(func_wrapper: IndexifyFunctionWrapper) -> bool:
125
+ def _func_is_reducer(func_wrapper: TensorlakeFunctionWrapper) -> bool:
150
126
  return func_wrapper.indexify_function.accumulate is not None
@@ -1,9 +1,7 @@
1
1
  from typing import Any
2
2
 
3
- from indexify.function_executor.proto.function_executor_pb2 import (
4
- RunTaskRequest,
5
- )
6
- from indexify.function_executor.proto.message_validator import MessageValidator
3
+ from ...proto.function_executor_pb2 import RunTaskRequest
4
+ from ...proto.message_validator import MessageValidator
7
5
 
8
6
 
9
7
  class RequestValidator:
@@ -17,7 +15,11 @@ class RequestValidator:
17
15
  Raises: ValueError: If the request is invalid.
18
16
  """
19
17
  (
20
- self._message_validator.required_field("graph_invocation_id")
18
+ self._message_validator.required_field("namespace")
19
+ .required_field("graph_name")
20
+ .required_field("graph_version")
21
+ .required_field("function_name")
22
+ .required_field("graph_invocation_id")
21
23
  .required_field("task_id")
22
24
  .required_serialized_object("function_input")
23
25
  .optional_serialized_object("function_init_value")
@@ -1,17 +1,15 @@
1
1
  from typing import List
2
2
 
3
- from indexify.function_executor.proto.function_executor_pb2 import (
3
+ from tensorlake.functions_sdk.data_objects import TensorlakeData
4
+ from tensorlake.functions_sdk.functions import FunctionCallResult, RouterCallResult
5
+ from tensorlake.functions_sdk.object_serializer import get_serializer
6
+
7
+ from ...proto.function_executor_pb2 import (
4
8
  FunctionOutput,
5
9
  RouterOutput,
6
10
  RunTaskResponse,
7
11
  SerializedObject,
8
12
  )
9
- from indexify.functions_sdk.data_objects import IndexifyData
10
- from indexify.functions_sdk.indexify_functions import (
11
- FunctionCallResult,
12
- RouterCallResult,
13
- )
14
- from indexify.functions_sdk.object_serializer import get_serializer
15
13
 
16
14
 
17
15
  class ResponseHelper:
@@ -81,7 +79,7 @@ class ResponseHelper:
81
79
  success=False,
82
80
  )
83
81
 
84
- def _to_function_output(self, outputs: List[IndexifyData]) -> FunctionOutput:
82
+ def _to_function_output(self, outputs: List[TensorlakeData]) -> FunctionOutput:
85
83
  output = FunctionOutput(outputs=[])
86
84
  for ix_data in outputs:
87
85
  serialized_object: SerializedObject = SerializedObject(
@@ -1,6 +1,5 @@
1
- from indexify.function_executor.proto.message_validator import MessageValidator
2
-
3
1
  from .proto.function_executor_pb2 import InitializeRequest
2
+ from .proto.message_validator import MessageValidator
4
3
 
5
4
 
6
5
  class InitializeRequestValidator:
@@ -2,7 +2,7 @@ import queue
2
2
  import threading
3
3
  from typing import Any, Iterator, Optional
4
4
 
5
- from indexify.functions_sdk.object_serializer import (
5
+ from tensorlake.functions_sdk.object_serializer import (
6
6
  CloudPickleSerializer,
7
7
  get_serializer,
8
8
  )
@@ -1,8 +1,6 @@
1
1
  from typing import Any, Optional
2
2
 
3
- from indexify.functions_sdk.invocation_state.invocation_state import (
4
- InvocationState,
5
- )
3
+ from tensorlake.functions_sdk.invocation_state.invocation_state import InvocationState
6
4
 
7
5
  from .invocation_state_proxy_server import InvocationStateProxyServer
8
6
 
@@ -0,0 +1,50 @@
1
+ from indexify.utils.logging import (
2
+ configure_development_mode_logging,
3
+ configure_logging_early,
4
+ configure_production_mode_logging,
5
+ )
6
+
7
+ configure_logging_early()
8
+
9
+ import argparse
10
+
11
+ import structlog
12
+
13
+ from .server import Server
14
+ from .service import Service
15
+
16
+ logger = structlog.get_logger(module=__name__)
17
+
18
+
19
+ def validate_args(args):
20
+ if args.address is None:
21
+ logger.error("--address argument is required")
22
+ exit(1)
23
+
24
+
25
+ def main():
26
+ parser = argparse.ArgumentParser(
27
+ description="Runs Function Executor with the specified API server address"
28
+ )
29
+ parser.add_argument("--address", help="API server address to listen on", type=str)
30
+ parser.add_argument(
31
+ "-d", "--dev", help="Run in development mode", action="store_true"
32
+ )
33
+ args = parser.parse_args()
34
+
35
+ if args.dev:
36
+ configure_development_mode_logging()
37
+ else:
38
+ configure_production_mode_logging()
39
+ validate_args(args)
40
+
41
+ logger.info("starting function executor server", address=args.address)
42
+
43
+ Server(
44
+ server_address=args.address,
45
+ service=Service(),
46
+ ).run()
47
+
48
+
49
+ if __name__ == "__main__":
50
+ main()
@@ -4,10 +4,18 @@
4
4
  # This is due to internal hard gRPC limits. When we want to increase the message sizes
5
5
  # we'll have to implement chunking for large messages.
6
6
  _MAX_GRPC_MESSAGE_LENGTH = -1
7
+ # Disable port reuse: fail if multiple Function Executor Servers attempt to bind to the
8
+ # same port. This happens when Indexify users misconfigure the Servers. Disabling the port
9
+ # reuse results in a clear error message on Server startup instead of obscure errors later
10
+ # while Indexify cluster is serving tasks.
11
+ # If we don't disable port reuse then a random Server gets the requests so wrong tasks get
12
+ # routed to wrong servers.
13
+ _REUSE_SERVER_PORT = 0
7
14
 
8
15
  GRPC_SERVER_OPTIONS = [
9
16
  ("grpc.max_receive_message_length", _MAX_GRPC_MESSAGE_LENGTH),
10
17
  ("grpc.max_send_message_length", _MAX_GRPC_MESSAGE_LENGTH),
18
+ ("grpc.so_reuseport", _REUSE_SERVER_PORT),
11
19
  ]
12
20
 
13
21
  GRPC_CHANNEL_OPTIONS = GRPC_SERVER_OPTIONS
@@ -24,13 +24,14 @@ message SerializedObject {
24
24
  message InitializeRequest {
25
25
  optional string namespace = 1;
26
26
  optional string graph_name = 2;
27
- optional int32 graph_version = 3;
27
+ optional string graph_version = 3;
28
28
  optional string function_name = 5;
29
29
  optional SerializedObject graph = 7;
30
30
  }
31
31
 
32
32
  message InitializeResponse {
33
33
  optional bool success = 1;
34
+ optional string customer_error = 2;
34
35
  }
35
36
 
36
37
  message SetInvocationStateRequest {
@@ -85,10 +86,14 @@ message RouterOutput {
85
86
  }
86
87
 
87
88
  message RunTaskRequest {
88
- optional string graph_invocation_id = 4;
89
+ optional string namespace = 1;
90
+ optional string graph_name = 2;
91
+ optional string graph_version = 3;
92
+ optional string function_name = 4;
93
+ optional string graph_invocation_id = 5;
89
94
  optional string task_id = 6;
90
- optional SerializedObject function_input = 9;
91
- optional SerializedObject function_init_value = 10;
95
+ optional SerializedObject function_input = 7;
96
+ optional SerializedObject function_init_value = 8;
92
97
  }
93
98
 
94
99
  message RunTaskResponse {