Skip to content

Commit df8e57b

Browse files
committed
Fix name conflict in runner context: introduce action cache and action handler cache inside of it, because handlers from different actions can have the same name
1 parent 71759d1 commit df8e57b

File tree

6 files changed

+97
-102
lines changed

6 files changed

+97
-102
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
from finecode_extension_runner import cli
22

33
if __name__ == "__main__":
4-
cli.cli()
4+
cli.main()

finecode_extension_runner/src/finecode_extension_runner/_services/run_action.py

Lines changed: 37 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -79,16 +79,19 @@ async def run_action(
7979
# returned. (experimental)
8080
# - execution of handlers can be concurrent or sequential. But executions of handler
8181
# on iterable payloads(single parts) are always concurrent.
82+
action_name = request.action_name
8283

8384
try:
84-
action_exec_info = global_state.runner_context.action_exec_info_by_name[
85-
request.action_name
86-
]
85+
action_cache = global_state.runner_context.action_cache_by_name[action_name]
8786
except KeyError:
87+
action_cache = domain.ActionCache()
88+
global_state.runner_context.action_cache_by_name[action_name] = action_cache
89+
90+
if action_cache.exec_info is not None:
91+
action_exec_info = action_cache.exec_info
92+
else:
8893
action_exec_info = create_action_exec_info(action)
89-
global_state.runner_context.action_exec_info_by_name[request.action_name] = (
90-
action_exec_info
91-
)
94+
action_cache.exec_info = action_exec_info
9295

9396
# TODO: catch validation errors
9497
payload: code_action.RunActionPayload | None = None
@@ -140,6 +143,7 @@ async def run_action(
140143
payload=payload,
141144
run_context=run_context,
142145
run_id=run_id,
146+
action_cache=action_cache,
143147
action_context=action_context,
144148
action_exec_info=action_exec_info,
145149
runner_context=runner_context,
@@ -223,6 +227,7 @@ async def run_action(
223227
payload=payload,
224228
run_context=run_context,
225229
run_id=run_id,
230+
action_cache=action_cache,
226231
action_context=action_context,
227232
action_exec_info=action_exec_info,
228233
runner_context=runner_context,
@@ -253,6 +258,7 @@ async def run_action(
253258
payload=payload,
254259
run_context=run_context,
255260
run_id=run_id,
261+
action_cache=action_cache,
256262
action_context=action_context,
257263
action_exec_info=action_exec_info,
258264
runner_context=runner_context,
@@ -379,19 +385,37 @@ async def execute_action_handler(
379385
run_context: code_action.RunActionContext | None,
380386
run_id: int,
381387
action_exec_info: domain.ActionExecInfo,
388+
action_cache: domain.ActionCache,
382389
action_context: code_action.ActionContext,
383390
runner_context: context.RunnerContext,
384391
) -> code_action.RunActionResult:
385392
logger.trace(f"R{run_id} | Run {handler.name} on {str(payload)[:100]}...")
393+
if handler.name in action_cache.handler_cache_by_name:
394+
handler_cache = action_cache.handler_cache_by_name[handler.name]
395+
else:
396+
handler_cache = domain.ActionHandlerCache()
397+
action_cache.handler_cache_by_name[handler.name] = handler_cache
398+
386399
start_time = time.time_ns()
387400
execution_result: code_action.RunActionResult | None = None
401+
402+
handler_global_config = runner_context.project.action_handler_configs.get(
403+
handler.source, None
404+
)
405+
handler_raw_config = {}
406+
if handler_global_config is not None:
407+
handler_raw_config = handler_global_config
408+
if handler_raw_config == {}:
409+
# still empty, just assign
410+
handler_raw_config = handler.config
411+
else:
412+
# not empty anymore, deep merge
413+
handler_config_merger.merge(handler_raw_config, handler.config)
388414

389-
if handler.name in runner_context.action_handlers_instances_by_name:
390-
handler_instance = runner_context.action_handlers_instances_by_name[
391-
handler.name
392-
]
415+
if handler_cache.instance is not None:
416+
handler_instance = handler_cache.instance
393417
handler_run_func = handler_instance.run
394-
exec_info = runner_context.action_handlers_exec_info_by_name[handler.name]
418+
exec_info = handler_cache.exec_info
395419
logger.trace(
396420
f"R{run_id} | Instance of action handler {handler.name} found in cache"
397421
)
@@ -411,19 +435,6 @@ async def execute_action_handler(
411435
f"Import of action handler '{handler.name}' failed(Run {run_id}): {handler.source}"
412436
)
413437

414-
handler_global_config = runner_context.project.action_handler_configs.get(
415-
handler.source, None
416-
)
417-
handler_raw_config = {}
418-
if handler_global_config is not None:
419-
handler_raw_config = handler_global_config
420-
if handler_raw_config == {}:
421-
# still empty, just assign
422-
handler_raw_config = handler.config
423-
else:
424-
# not empty anymore, deep merge
425-
handler_config_merger.merge(handler_raw_config, handler.config)
426-
427438
def get_handler_config(param_type):
428439
# TODO: validation errors
429440
return param_type(**handler_raw_config)
@@ -437,7 +448,7 @@ def get_process_executor(param_type):
437448
exec_info = domain.ActionHandlerExecInfo()
438449
# save immediately in context to be able to shutdown it if the first execution
439450
# is interrupted by stopping ER
440-
runner_context.action_handlers_exec_info_by_name[handler.name] = exec_info
451+
handler_cache.exec_info = exec_info
441452
if inspect.isclass(action_handler):
442453
args = resolve_func_args_with_di(
443454
func=action_handler.__init__,
@@ -453,9 +464,7 @@ def get_process_executor(param_type):
453464
exec_info.lifecycle = args["lifecycle"]
454465

455466
handler_instance = action_handler(**args)
456-
runner_context.action_handlers_instances_by_name[handler.name] = (
457-
handler_instance
458-
)
467+
handler_cache.instance = handler_instance
459468
handler_run_func = handler_instance.run
460469
else:
461470
handler_run_func = action_handler
Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,12 @@
1-
from dataclasses import dataclass, field
1+
from __future__ import annotations
22

3-
from finecode_extension_api import code_action
3+
from dataclasses import dataclass, field
44
from finecode_extension_runner import domain
55

66

77
@dataclass
88
class RunnerContext:
99
project: domain.Project
10-
action_exec_info_by_name: dict[str, domain.ActionExecInfo] = field(
11-
default_factory=dict
12-
)
13-
action_handlers_instances_by_name: dict[str, code_action.ActionHandler] = field(
14-
default_factory=dict
15-
)
16-
action_handlers_exec_info_by_name: dict[str, domain.ActionHandlerExecInfo] = field(
17-
default_factory=dict
18-
)
10+
action_cache_by_name: dict[str, domain.ActionCache] = field(default_factory=dict)
1911
# don't overwrite, only append and remove
2012
docs_owned_by_client: list[str] = field(default_factory=list)

finecode_extension_runner/src/finecode_extension_runner/di/resolver.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Any, Callable, Type, TypeVar
1+
from typing import Type, TypeVar
22

33
from finecode_extension_api import code_action
44

finecode_extension_runner/src/finecode_extension_runner/domain.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import enum
4+
import dataclasses
45
import typing
56
from pathlib import Path
67

@@ -76,6 +77,18 @@ class ActionHandlerExecInfoStatus(enum.Enum):
7677
SHUTDOWN = enum.auto()
7778

7879

80+
@dataclasses.dataclass
81+
class ActionCache:
82+
exec_info: ActionExecInfo | None = None
83+
handler_cache_by_name: dict[str, ActionHandlerCache] = dataclasses.field(default_factory=dict)
84+
85+
86+
@dataclasses.dataclass
87+
class ActionHandlerCache:
88+
instance: code_action.ActionHandler | None = None
89+
exec_info: ActionHandlerExecInfo | None = None
90+
91+
7992
class TextDocumentInfo:
8093
def __init__(self, uri: str, version: str, text: str) -> None:
8194
self.uri = uri

finecode_extension_runner/src/finecode_extension_runner/services.py

Lines changed: 42 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,12 @@
1-
import asyncio
2-
import collections.abc
31
import importlib
4-
import inspect
52
import sys
6-
import time
73
import types
84
import typing
95
from pathlib import Path
106

117
from loguru import logger
12-
from pydantic.dataclasses import dataclass as pydantic_dataclass
138

14-
from finecode_extension_api import code_action, textstyler
15-
from finecode_extension_runner import context, domain, global_state, run_utils, schemas
16-
from finecode_extension_runner._services import run_action as run_action_module
9+
from finecode_extension_runner import context, domain, global_state, schemas
1710
from finecode_extension_runner._services.run_action import (
1811
ActionFailedException,
1912
StopWithResponse,
@@ -96,49 +89,37 @@ def reload_action(action_name: str) -> None:
9689
# TODO: raise error
9790
return
9891

99-
actions_to_remove: list[str] = [
100-
action_name,
101-
*[handler.name for handler in action_obj.handlers],
102-
]
92+
if action_name in global_state.runner_context.action_cache_by_name:
93+
action_cache = global_state.runner_context.action_cache_by_name[action_name]
10394

104-
for _action_name in actions_to_remove:
105-
try:
106-
del global_state.runner_context.action_handlers_instances_by_name[
107-
_action_name
108-
]
109-
logger.trace(f"Removed '{_action_name}' instance from cache")
110-
except KeyError:
111-
logger.info(
112-
f"Tried to reload action '{_action_name}', but it was not found"
113-
)
95+
for handler_name, handler_cache in action_cache.handler_cache_by_name.items():
96+
if handler_cache.exec_info is not None:
97+
shutdown_action_handler(
98+
action_handler_name=handler_name,
99+
exec_info=handler_cache.exec_info,
100+
)
114101

115-
if (
116-
_action_name
117-
in global_state.runner_context.action_handlers_exec_info_by_name
118-
):
119-
shutdown_action_handler(
120-
action_handler_name=_action_name,
121-
exec_info=global_state.runner_context.action_handlers_exec_info_by_name[
122-
_action_name
123-
],
124-
)
102+
del global_state.runner_context.action_cache_by_name[action_name]
103+
logger.trace(f"Removed '{action_name}' instance from cache")
125104

126-
try:
127-
action_obj = project_def.actions[action_name]
128-
except KeyError:
129-
logger.warning(f"Definition of action {action_name} not found")
130-
continue
105+
try:
106+
action_obj = project_def.actions[action_name]
107+
except KeyError:
108+
logger.warning(f"Definition of action {action_name} not found")
109+
return
131110

132-
action_source = action_obj.source
133-
if action_source is None:
134-
continue
135-
action_package = action_source.split(".")[0]
111+
sources_to_remove = [action_obj.source]
112+
for handler in action_obj.handlers:
113+
sources_to_remove.append(handler.source)
114+
115+
for source_to_remove in sources_to_remove:
116+
source_package = source_to_remove.split(".")[0]
136117

137118
loaded_package_modules = dict(
138119
[
139120
(key, value)
140121
for key, value in sys.modules.items()
141-
if key.startswith(action_package)
122+
if key.startswith(source_package)
142123
and isinstance(value, types.ModuleType)
143124
]
144125
)
@@ -147,7 +128,7 @@ def reload_action(action_name: str) -> None:
147128
for key in loaded_package_modules:
148129
del sys.modules[key]
149130

150-
logger.trace(f"Remove modules of package '{action_package}' from cache")
131+
logger.trace(f"Remove modules of package '{source_package}' from cache")
151132

152133

153134
def resolve_package_path(package_name: str) -> str:
@@ -162,11 +143,13 @@ def resolve_package_path(package_name: str) -> str:
162143

163144

164145
def document_did_open(document_uri: str) -> None:
165-
global_state.runner_context.docs_owned_by_client.append(document_uri)
146+
if global_state.runner_context is not None:
147+
global_state.runner_context.docs_owned_by_client.append(document_uri)
166148

167149

168150
def document_did_close(document_uri: str) -> None:
169-
global_state.runner_context.docs_owned_by_client.remove(document_uri)
151+
if global_state.runner_context is not None:
152+
global_state.runner_context.docs_owned_by_client.remove(document_uri)
170153

171154

172155
def shutdown_action_handler(
@@ -190,19 +173,17 @@ def shutdown_action_handler(
190173

191174
def shutdown_all_action_handlers() -> None:
192175
logger.trace("Shutdown all action handlers")
193-
for (
194-
action_handler_name,
195-
exec_info,
196-
) in global_state.runner_context.action_handlers_exec_info_by_name.items():
197-
shutdown_action_handler(
198-
action_handler_name=action_handler_name, exec_info=exec_info
199-
)
176+
for action_cache in global_state.action_cache_by_name.values():
177+
for handler_name, handler_cache in action_cache.handler_cache_by_name.items():
178+
if handler_cache.exec_info is not None:
179+
shutdown_action_handler(
180+
action_handler_name=handler_name, exec_info=handler_cache.exec_info
181+
)
200182

201183

202184
def exit_action_handler(
203185
action_handler_name: str, exec_info: domain.ActionHandlerExecInfo
204186
) -> None:
205-
# action handler exec info expected to exist in runner_context
206187
if (
207188
exec_info.lifecycle is not None
208189
and exec_info.lifecycle.on_exit_callable is not None
@@ -216,11 +197,11 @@ def exit_action_handler(
216197

217198
def exit_all_action_handlers() -> None:
218199
logger.trace("Exit all action handlers")
219-
for (
220-
action_handler_name,
221-
exec_info,
222-
) in global_state.runner_context.action_handlers_exec_info_by_name.items():
223-
exit_action_handler(
224-
action_handler_name=action_handler_name, exec_info=exec_info
225-
)
226-
global_state.runner_context.action_handlers_exec_info_by_name = {}
200+
for action_cache in global_state.action_cache_by_name.values():
201+
for handler_name, handler_cache in action_cache.handler_cache_by_name.items():
202+
if handler_cache.exec_info is not None:
203+
exec_info = handler_cache.exec_info
204+
exit_action_handler(
205+
action_handler_name=handler_name, exec_info=exec_info
206+
)
207+
action_cache.handler_cache_by_name = {}

0 commit comments

Comments
 (0)