langfun 0.1.2.dev202509230805__py3-none-any.whl → 0.1.2.dev202509250804__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.
Potentially problematic release.
This version of langfun might be problematic. Click here for more details.
- langfun/env/__init__.py +1 -1
- langfun/env/base_environment.py +70 -39
- langfun/env/base_feature.py +25 -12
- langfun/env/base_sandbox.py +204 -74
- langfun/env/base_test.py +166 -399
- langfun/env/interface.py +167 -35
- langfun/env/test_utils.py +473 -0
- {langfun-0.1.2.dev202509230805.dist-info → langfun-0.1.2.dev202509250804.dist-info}/METADATA +1 -1
- {langfun-0.1.2.dev202509230805.dist-info → langfun-0.1.2.dev202509250804.dist-info}/RECORD +12 -11
- {langfun-0.1.2.dev202509230805.dist-info → langfun-0.1.2.dev202509250804.dist-info}/WHEEL +0 -0
- {langfun-0.1.2.dev202509230805.dist-info → langfun-0.1.2.dev202509250804.dist-info}/licenses/LICENSE +0 -0
- {langfun-0.1.2.dev202509230805.dist-info → langfun-0.1.2.dev202509250804.dist-info}/top_level.txt +0 -0
langfun/env/__init__.py
CHANGED
|
@@ -22,7 +22,7 @@ from langfun.env.interface import EnvironmentOutageError
|
|
|
22
22
|
from langfun.env.interface import EnvironmentOverloadError
|
|
23
23
|
from langfun.env.interface import SandboxError
|
|
24
24
|
from langfun.env.interface import SandboxStateError
|
|
25
|
-
|
|
25
|
+
from langfun.env.interface import EnvironmentEventHandler
|
|
26
26
|
|
|
27
27
|
from langfun.env.interface import Environment
|
|
28
28
|
from langfun.env.interface import Sandbox
|
langfun/env/base_environment.py
CHANGED
|
@@ -60,7 +60,7 @@ class BaseEnvironment(interface.Environment):
|
|
|
60
60
|
'will be used as both min and max size. If 0, sandboxes will be '
|
|
61
61
|
'created on demand and shutdown when user session ends.'
|
|
62
62
|
)
|
|
63
|
-
] =
|
|
63
|
+
] = (0, 256)
|
|
64
64
|
|
|
65
65
|
load_balancer: Annotated[
|
|
66
66
|
load_balancers.LoadBalancer,
|
|
@@ -132,7 +132,7 @@ class BaseEnvironment(interface.Environment):
|
|
|
132
132
|
random if self.random_seed is None else random.Random(self.random_seed)
|
|
133
133
|
)
|
|
134
134
|
|
|
135
|
-
self.
|
|
135
|
+
self._housekeep_thread = None
|
|
136
136
|
self._offline_start_time = None
|
|
137
137
|
|
|
138
138
|
#
|
|
@@ -170,6 +170,11 @@ class BaseEnvironment(interface.Environment):
|
|
|
170
170
|
).hex[:7]
|
|
171
171
|
return f'session-{suffix}'
|
|
172
172
|
|
|
173
|
+
@property
|
|
174
|
+
def housekeep_counter(self) -> int:
|
|
175
|
+
"""Returns the housekeeping counter."""
|
|
176
|
+
return self._housekeep_counter
|
|
177
|
+
|
|
173
178
|
#
|
|
174
179
|
# Subclasses can override:
|
|
175
180
|
#
|
|
@@ -189,36 +194,36 @@ class BaseEnvironment(interface.Environment):
|
|
|
189
194
|
def _start(self) -> None:
|
|
190
195
|
"""Implementation of starting the environment."""
|
|
191
196
|
if self.min_pool_size > 0:
|
|
192
|
-
self._sandbox_pool = [
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
]
|
|
197
|
+
self._sandbox_pool = [None] * self.min_pool_size
|
|
198
|
+
for i, sandbox, _ in lf.concurrent_map(
|
|
199
|
+
lambda i: self._bring_up_sandbox_with_retry(
|
|
200
|
+
sandbox_id=str(i), shutdown_env_upon_outage=False
|
|
201
|
+
),
|
|
202
|
+
range(self.min_pool_size),
|
|
203
|
+
silence_on_errors=None,
|
|
204
|
+
max_workers=min(
|
|
205
|
+
self.pool_operation_max_parallelism,
|
|
206
|
+
self.min_pool_size
|
|
207
|
+
),
|
|
208
|
+
):
|
|
209
|
+
self._sandbox_pool[i] = sandbox
|
|
206
210
|
self._next_sandbox_id = len(self._sandbox_pool)
|
|
207
|
-
self.
|
|
208
|
-
target=self.
|
|
211
|
+
self._housekeep_thread = threading.Thread(
|
|
212
|
+
target=self._housekeep_loop, daemon=True
|
|
209
213
|
)
|
|
210
|
-
self.
|
|
211
|
-
self.
|
|
214
|
+
self._housekeep_counter = 0
|
|
215
|
+
self._housekeep_thread.start()
|
|
212
216
|
|
|
213
217
|
def _shutdown(self) -> None:
|
|
214
218
|
"""Implementation of shutting down the environment."""
|
|
215
|
-
if (self.
|
|
216
|
-
and threading.current_thread() is not self.
|
|
217
|
-
self.
|
|
218
|
-
self.
|
|
219
|
+
if (self._housekeep_thread is not None
|
|
220
|
+
and threading.current_thread() is not self._housekeep_thread):
|
|
221
|
+
self._housekeep_thread.join()
|
|
222
|
+
self._housekeep_thread = None
|
|
219
223
|
|
|
220
224
|
def _shutdown_sandbox(sandbox: base_sandbox.BaseSandbox) -> None:
|
|
221
|
-
sandbox
|
|
225
|
+
if sandbox is not None:
|
|
226
|
+
sandbox.shutdown()
|
|
222
227
|
|
|
223
228
|
if self._sandbox_pool:
|
|
224
229
|
_ = list(
|
|
@@ -303,18 +308,19 @@ class BaseEnvironment(interface.Environment):
|
|
|
303
308
|
f'it is in {self._status.value!r} status.'
|
|
304
309
|
)
|
|
305
310
|
|
|
311
|
+
starting_time = time.time()
|
|
306
312
|
try:
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
313
|
+
self._start()
|
|
314
|
+
self._start_time = time.time()
|
|
315
|
+
self._set_status(self.Status.ONLINE)
|
|
316
|
+
self.on_start(duration=time.time() - starting_time)
|
|
317
|
+
|
|
310
318
|
pg.logging.info(
|
|
311
319
|
'[%s]: %s started in %.2f seconds.',
|
|
312
|
-
self.id, self.__class__.__name__,
|
|
320
|
+
self.id, self.__class__.__name__, time.time() - starting_time
|
|
313
321
|
)
|
|
314
|
-
self._set_status(self.Status.ONLINE)
|
|
315
|
-
self.on_start()
|
|
316
322
|
except BaseException as e:
|
|
317
|
-
self.on_start(error=e)
|
|
323
|
+
self.on_start(duration=time.time() - starting_time, error=e)
|
|
318
324
|
self.shutdown()
|
|
319
325
|
raise e
|
|
320
326
|
|
|
@@ -474,13 +480,14 @@ class BaseEnvironment(interface.Environment):
|
|
|
474
480
|
# Environment maintenance loop.
|
|
475
481
|
#
|
|
476
482
|
|
|
477
|
-
def
|
|
478
|
-
"""
|
|
483
|
+
def _housekeep_loop(self) -> None:
|
|
484
|
+
"""Housekeeping loop for the environment."""
|
|
479
485
|
pg.logging.info(
|
|
480
486
|
'[%s]: %s maintenance thread started.', self.id, self.__class__.__name__
|
|
481
487
|
)
|
|
482
488
|
stats_report_time = time.time()
|
|
483
489
|
while self._status not in (self.Status.SHUTTING_DOWN, self.Status.OFFLINE):
|
|
490
|
+
housekeep_start_time = time.time()
|
|
484
491
|
if time.time() - stats_report_time > self.stats_report_interval:
|
|
485
492
|
pg.logging.info(
|
|
486
493
|
'[%s] %s stats: %s.',
|
|
@@ -496,9 +503,17 @@ class BaseEnvironment(interface.Environment):
|
|
|
496
503
|
dead_pool_indices
|
|
497
504
|
):
|
|
498
505
|
self.shutdown()
|
|
499
|
-
self.
|
|
506
|
+
self._housekeep_counter += 1
|
|
507
|
+
self.on_housekeep(
|
|
508
|
+
time.time() - housekeep_start_time,
|
|
509
|
+
interface.EnvironmentOutageError(
|
|
510
|
+
environment=self,
|
|
511
|
+
offline_duration=self.offline_duration
|
|
512
|
+
)
|
|
513
|
+
)
|
|
500
514
|
break
|
|
501
|
-
self.
|
|
515
|
+
self._housekeep_counter += 1
|
|
516
|
+
self.on_housekeep(time.time() - housekeep_start_time)
|
|
502
517
|
time.sleep(1)
|
|
503
518
|
|
|
504
519
|
def _replace_dead_sandboxes(self, dead_pool_indices: list[int]) -> bool:
|
|
@@ -526,6 +541,8 @@ class BaseEnvironment(interface.Environment):
|
|
|
526
541
|
str(i), shutdown_env_upon_outage=False
|
|
527
542
|
)
|
|
528
543
|
|
|
544
|
+
# TODO(daiyip): Consider to loose the condition to allow some dead
|
|
545
|
+
# sandboxes to be replaced successfully.
|
|
529
546
|
return not any([
|
|
530
547
|
error for _, _, error in lf.concurrent_map(
|
|
531
548
|
_replace, dead_pool_indices,
|
|
@@ -540,12 +557,26 @@ class BaseEnvironment(interface.Environment):
|
|
|
540
557
|
# Event handlers subclasses can override.
|
|
541
558
|
#
|
|
542
559
|
|
|
543
|
-
def on_start(
|
|
560
|
+
def on_start(
|
|
561
|
+
self,
|
|
562
|
+
duration: float, error: BaseException | None = None
|
|
563
|
+
) -> None:
|
|
544
564
|
"""Called when the environment is started."""
|
|
545
565
|
for handler in self.event_handlers:
|
|
546
|
-
handler.on_environment_start(self, error)
|
|
566
|
+
handler.on_environment_start(self, duration, error)
|
|
567
|
+
|
|
568
|
+
def on_housekeep(
|
|
569
|
+
self,
|
|
570
|
+
duration: float,
|
|
571
|
+
error: BaseException | None = None
|
|
572
|
+
) -> None:
|
|
573
|
+
"""Called when the environment finishes a round of housekeeping."""
|
|
574
|
+
housekeep_counter = self.housekeep_counter
|
|
575
|
+
for handler in self.event_handlers:
|
|
576
|
+
handler.on_environment_housekeep(self, housekeep_counter, duration, error)
|
|
547
577
|
|
|
548
578
|
def on_shutdown(self, error: BaseException | None = None) -> None:
|
|
549
579
|
"""Called when the environment is shutdown."""
|
|
580
|
+
lifetime = (time.time() - self.start_time) if self.start_time else 0.0
|
|
550
581
|
for handler in self.event_handlers:
|
|
551
|
-
handler.on_environment_shutdown(self, error)
|
|
582
|
+
handler.on_environment_shutdown(self, lifetime, error)
|
langfun/env/base_feature.py
CHANGED
|
@@ -23,6 +23,7 @@ the `Environment` and `Sandbox` interfaces directly.
|
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
25
|
import functools
|
|
26
|
+
import time
|
|
26
27
|
from typing import Annotated, Callable
|
|
27
28
|
|
|
28
29
|
from langfun.env import interface
|
|
@@ -82,6 +83,7 @@ class BaseFeature(interface.Feature):
|
|
|
82
83
|
"""Called when the feature is bound."""
|
|
83
84
|
super()._on_bound()
|
|
84
85
|
self._sandbox = None
|
|
86
|
+
self._housekeep_counter = 0
|
|
85
87
|
|
|
86
88
|
@functools.cached_property
|
|
87
89
|
def name(self) -> str:
|
|
@@ -115,13 +117,14 @@ class BaseFeature(interface.Feature):
|
|
|
115
117
|
) -> None:
|
|
116
118
|
"""Triggers an event handler."""
|
|
117
119
|
error = None
|
|
120
|
+
start_time = time.time()
|
|
118
121
|
try:
|
|
119
122
|
action()
|
|
120
123
|
except BaseException as e: # pylint: disable=broad-except
|
|
121
124
|
error = e
|
|
122
125
|
raise
|
|
123
126
|
finally:
|
|
124
|
-
event_handler(error=error)
|
|
127
|
+
event_handler(duration=time.time() - start_time, error=error)
|
|
125
128
|
|
|
126
129
|
def setup(self, sandbox: interface.Sandbox) -> None:
|
|
127
130
|
"""Sets up the feature."""
|
|
@@ -146,7 +149,10 @@ class BaseFeature(interface.Feature):
|
|
|
146
149
|
|
|
147
150
|
def housekeep(self) -> None:
|
|
148
151
|
"""Performs housekeeping for the feature."""
|
|
149
|
-
|
|
152
|
+
try:
|
|
153
|
+
self._do(self._housekeep, self.on_housekeep)
|
|
154
|
+
finally:
|
|
155
|
+
self._housekeep_counter += 1
|
|
150
156
|
|
|
151
157
|
#
|
|
152
158
|
# Event handlers subclasses can override.
|
|
@@ -154,51 +160,58 @@ class BaseFeature(interface.Feature):
|
|
|
154
160
|
|
|
155
161
|
def on_setup(
|
|
156
162
|
self,
|
|
163
|
+
duration: float,
|
|
157
164
|
error: BaseException | None = None
|
|
158
165
|
) -> None:
|
|
159
166
|
"""Called when the feature is setup."""
|
|
160
|
-
self.sandbox.on_feature_setup(self, error)
|
|
167
|
+
self.sandbox.on_feature_setup(self, duration, error)
|
|
161
168
|
|
|
162
169
|
def on_teardown(
|
|
163
170
|
self,
|
|
171
|
+
duration: float,
|
|
164
172
|
error: BaseException | None = None
|
|
165
173
|
) -> None:
|
|
166
174
|
"""Called when the feature is teardown."""
|
|
167
|
-
self.sandbox.on_feature_teardown(self, error)
|
|
175
|
+
self.sandbox.on_feature_teardown(self, duration, error)
|
|
168
176
|
|
|
169
177
|
def on_housekeep(
|
|
170
178
|
self,
|
|
179
|
+
duration: float,
|
|
171
180
|
error: BaseException | None = None
|
|
172
181
|
) -> None:
|
|
173
182
|
"""Called when the feature has done housekeeping."""
|
|
174
|
-
self.sandbox.on_feature_housekeep(
|
|
183
|
+
self.sandbox.on_feature_housekeep(
|
|
184
|
+
self, self._housekeep_counter, duration, error
|
|
185
|
+
)
|
|
175
186
|
|
|
176
187
|
def on_setup_session(
|
|
177
188
|
self,
|
|
189
|
+
duration: float,
|
|
178
190
|
error: BaseException | None = None,
|
|
179
191
|
) -> None:
|
|
180
192
|
"""Called when the feature is setup for a user session."""
|
|
181
|
-
self.sandbox.on_feature_setup_session(self, error)
|
|
193
|
+
self.sandbox.on_feature_setup_session(self, duration, error)
|
|
182
194
|
|
|
183
195
|
def on_teardown_session(
|
|
184
196
|
self,
|
|
197
|
+
duration: float,
|
|
185
198
|
error: BaseException | None = None,
|
|
186
199
|
) -> None:
|
|
187
200
|
"""Called when the feature is teardown for a user session."""
|
|
188
|
-
self.sandbox.on_feature_teardown_session(self, error)
|
|
201
|
+
self.sandbox.on_feature_teardown_session(self, duration, error)
|
|
189
202
|
|
|
190
|
-
def
|
|
203
|
+
def on_activity(
|
|
191
204
|
self,
|
|
192
|
-
session_id: str,
|
|
193
205
|
name: str,
|
|
206
|
+
duration: float,
|
|
194
207
|
error: BaseException | None,
|
|
195
208
|
**kwargs
|
|
196
209
|
) -> None:
|
|
197
210
|
"""Called when a sandbox activity is performed."""
|
|
198
|
-
self.sandbox.
|
|
199
|
-
|
|
200
|
-
name=name,
|
|
211
|
+
self.sandbox.on_activity(
|
|
212
|
+
name=f'{self.name}.{name}',
|
|
201
213
|
feature=self,
|
|
202
214
|
error=error,
|
|
215
|
+
duration=duration,
|
|
203
216
|
**kwargs
|
|
204
217
|
)
|