langfun 0.1.2.dev202509250804__py3-none-any.whl → 0.1.2.dev202509270803__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/core/llms/__init__.py +2 -0
- langfun/core/llms/openai.py +57 -0
- langfun/env/__init__.py +4 -5
- langfun/env/base_environment.py +41 -34
- langfun/env/base_feature.py +10 -1
- langfun/env/base_sandbox.py +27 -26
- langfun/env/base_test.py +106 -33
- langfun/env/event_handlers/__init__.py +12 -0
- langfun/env/event_handlers/base.py +271 -0
- langfun/env/event_handlers/event_logger.py +415 -0
- langfun/env/event_handlers/event_logger_test.py +289 -0
- langfun/env/interface.py +103 -380
- langfun/env/interface_test.py +3 -3
- langfun/env/load_balancers_test.py +3 -15
- langfun/env/test_utils.py +12 -26
- {langfun-0.1.2.dev202509250804.dist-info → langfun-0.1.2.dev202509270803.dist-info}/METADATA +1 -1
- {langfun-0.1.2.dev202509250804.dist-info → langfun-0.1.2.dev202509270803.dist-info}/RECORD +20 -16
- {langfun-0.1.2.dev202509250804.dist-info → langfun-0.1.2.dev202509270803.dist-info}/WHEEL +0 -0
- {langfun-0.1.2.dev202509250804.dist-info → langfun-0.1.2.dev202509270803.dist-info}/licenses/LICENSE +0 -0
- {langfun-0.1.2.dev202509250804.dist-info → langfun-0.1.2.dev202509270803.dist-info}/top_level.txt +0 -0
langfun/core/llms/__init__.py
CHANGED
|
@@ -99,6 +99,8 @@ VertexAIGeminiFlash1_5 = VertexAIGemini15Flash
|
|
|
99
99
|
# OpenAI models.
|
|
100
100
|
from langfun.core.llms.openai import OpenAI
|
|
101
101
|
|
|
102
|
+
from langfun.core.llms.openai import Gpt5
|
|
103
|
+
from langfun.core.llms.openai import Gpt5Mini
|
|
102
104
|
from langfun.core.llms.openai import Gpt41
|
|
103
105
|
from langfun.core.llms.openai import GptO3
|
|
104
106
|
from langfun.core.llms.openai import GptO4Mini
|
langfun/core/llms/openai.py
CHANGED
|
@@ -49,6 +49,53 @@ class OpenAIModelInfo(lf.ModelInfo):
|
|
|
49
49
|
#
|
|
50
50
|
|
|
51
51
|
SUPPORTED_MODELS = [
|
|
52
|
+
# GPT-5 models
|
|
53
|
+
OpenAIModelInfo(
|
|
54
|
+
model_id='gpt-5',
|
|
55
|
+
alias_for='gpt-5-2025-08-07',
|
|
56
|
+
in_service=True,
|
|
57
|
+
model_type='instruction-tuned',
|
|
58
|
+
description='GPT 5 model (latest stable).',
|
|
59
|
+
url='https://platform.openai.com/docs/models/gpt-5',
|
|
60
|
+
input_modalities=OpenAIModelInfo.INPUT_IMAGE_TYPES,
|
|
61
|
+
context_length=lf.ModelInfo.ContextLength(
|
|
62
|
+
max_input_tokens=400_000,
|
|
63
|
+
max_output_tokens=128_000,
|
|
64
|
+
),
|
|
65
|
+
pricing=lf.ModelInfo.Pricing(
|
|
66
|
+
cost_per_1m_cached_input_tokens=0.125,
|
|
67
|
+
cost_per_1m_input_tokens=1.25,
|
|
68
|
+
cost_per_1m_output_tokens=10.0,
|
|
69
|
+
),
|
|
70
|
+
# Tier 5 rate limits.
|
|
71
|
+
rate_limits=lf.ModelInfo.RateLimits(
|
|
72
|
+
max_requests_per_minute=15_000,
|
|
73
|
+
max_tokens_per_minute=40_000_000,
|
|
74
|
+
),
|
|
75
|
+
),
|
|
76
|
+
OpenAIModelInfo(
|
|
77
|
+
model_id='gpt-5-mini',
|
|
78
|
+
alias_for='gpt-5-mini-2025-08-07',
|
|
79
|
+
in_service=True,
|
|
80
|
+
model_type='instruction-tuned',
|
|
81
|
+
description='GPT 5 mini model (latest stable).',
|
|
82
|
+
url='https://platform.openai.com/docs/models/gpt-5-mini',
|
|
83
|
+
input_modalities=OpenAIModelInfo.INPUT_IMAGE_TYPES,
|
|
84
|
+
context_length=lf.ModelInfo.ContextLength(
|
|
85
|
+
max_input_tokens=400_000,
|
|
86
|
+
max_output_tokens=128_000,
|
|
87
|
+
),
|
|
88
|
+
pricing=lf.ModelInfo.Pricing(
|
|
89
|
+
cost_per_1m_cached_input_tokens=0.025,
|
|
90
|
+
cost_per_1m_input_tokens=0.25,
|
|
91
|
+
cost_per_1m_output_tokens=2.0,
|
|
92
|
+
),
|
|
93
|
+
# Tier 5 rate limits.
|
|
94
|
+
rate_limits=lf.ModelInfo.RateLimits(
|
|
95
|
+
max_requests_per_minute=180_000_000,
|
|
96
|
+
max_tokens_per_minute=30_000_000,
|
|
97
|
+
),
|
|
98
|
+
),
|
|
52
99
|
# GPT-4.1 models
|
|
53
100
|
OpenAIModelInfo(
|
|
54
101
|
model_id='gpt-4.1',
|
|
@@ -1069,6 +1116,16 @@ class OpenAI(openai_compatible.OpenAICompatible):
|
|
|
1069
1116
|
return super()._request_args(options)
|
|
1070
1117
|
|
|
1071
1118
|
|
|
1119
|
+
class Gpt5(OpenAI):
|
|
1120
|
+
"""GPT-5."""
|
|
1121
|
+
model = 'gpt-5'
|
|
1122
|
+
|
|
1123
|
+
|
|
1124
|
+
class Gpt5Mini(OpenAI):
|
|
1125
|
+
"""GPT-5 mini."""
|
|
1126
|
+
model = 'gpt-5-mini'
|
|
1127
|
+
|
|
1128
|
+
|
|
1072
1129
|
class Gpt41(OpenAI):
|
|
1073
1130
|
"""GPT-4.1."""
|
|
1074
1131
|
model = 'gpt-4.1'
|
langfun/env/__init__.py
CHANGED
|
@@ -13,16 +13,12 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
"""Environment for LLM agents."""
|
|
15
15
|
|
|
16
|
-
# pylint: disable=g-importing-member, g-bad-import-order
|
|
17
|
-
from langfun.env.interface import EnvironmentId
|
|
18
|
-
from langfun.env.interface import SandboxId
|
|
19
|
-
|
|
16
|
+
# pylint: disable=g-importing-member, g-bad-import-order, g-import-not-at-top
|
|
20
17
|
from langfun.env.interface import EnvironmentError # pylint: disable=redefined-builtin
|
|
21
18
|
from langfun.env.interface import EnvironmentOutageError
|
|
22
19
|
from langfun.env.interface import EnvironmentOverloadError
|
|
23
20
|
from langfun.env.interface import SandboxError
|
|
24
21
|
from langfun.env.interface import SandboxStateError
|
|
25
|
-
from langfun.env.interface import EnvironmentEventHandler
|
|
26
22
|
|
|
27
23
|
from langfun.env.interface import Environment
|
|
28
24
|
from langfun.env.interface import Sandbox
|
|
@@ -35,4 +31,7 @@ from langfun.env.base_feature import BaseFeature
|
|
|
35
31
|
from langfun.env import load_balancers
|
|
36
32
|
from langfun.env.load_balancers import LoadBalancer
|
|
37
33
|
|
|
34
|
+
from langfun.env import event_handlers
|
|
35
|
+
EventHandler = event_handlers.EventHandler
|
|
36
|
+
|
|
38
37
|
# Google-internal imports.
|
langfun/env/base_environment.py
CHANGED
|
@@ -34,6 +34,7 @@ import langfun.core as lf
|
|
|
34
34
|
from langfun.env import base_sandbox
|
|
35
35
|
from langfun.env import interface
|
|
36
36
|
from langfun.env import load_balancers
|
|
37
|
+
from langfun.env.event_handlers import base as event_handler_base
|
|
37
38
|
import pyglove as pg
|
|
38
39
|
|
|
39
40
|
|
|
@@ -65,10 +66,20 @@ class BaseEnvironment(interface.Environment):
|
|
|
65
66
|
load_balancer: Annotated[
|
|
66
67
|
load_balancers.LoadBalancer,
|
|
67
68
|
(
|
|
68
|
-
'The load balancer for the environment.'
|
|
69
|
+
'The load balancer for the environment to acquire sandboxes.'
|
|
69
70
|
)
|
|
70
71
|
] = load_balancers.RoundRobin()
|
|
71
72
|
|
|
73
|
+
sandbox_keepalive_interval: Annotated[
|
|
74
|
+
float | None,
|
|
75
|
+
(
|
|
76
|
+
'The interval in seconds to send keepalive pings to sandboxes. '
|
|
77
|
+
'If None, sandbox keepalive is disabled. Please note that sandbox '
|
|
78
|
+
'keepalive is different from feature housekeeping. Usually sandbox '
|
|
79
|
+
'keepalive and feature housekeeping are different operations.'
|
|
80
|
+
)
|
|
81
|
+
] = None
|
|
82
|
+
|
|
72
83
|
proactive_session_setup: Annotated[
|
|
73
84
|
bool,
|
|
74
85
|
(
|
|
@@ -79,6 +90,13 @@ class BaseEnvironment(interface.Environment):
|
|
|
79
90
|
)
|
|
80
91
|
] = True
|
|
81
92
|
|
|
93
|
+
event_handlers: Annotated[
|
|
94
|
+
list[event_handler_base.EventHandler],
|
|
95
|
+
(
|
|
96
|
+
'User handler for the environment events.'
|
|
97
|
+
)
|
|
98
|
+
] = []
|
|
99
|
+
|
|
82
100
|
outage_grace_period: Annotated[
|
|
83
101
|
float,
|
|
84
102
|
(
|
|
@@ -97,13 +115,15 @@ class BaseEnvironment(interface.Environment):
|
|
|
97
115
|
)
|
|
98
116
|
] = 10.0
|
|
99
117
|
|
|
100
|
-
|
|
118
|
+
housekeep_interval: Annotated[
|
|
101
119
|
float,
|
|
102
120
|
(
|
|
103
|
-
'The interval in seconds for
|
|
104
|
-
'
|
|
121
|
+
'The interval in seconds for environment housekeeping. It recycles '
|
|
122
|
+
'the dead sandboxes in the pool. This interval is the minimal time '
|
|
123
|
+
'to detect outage while there is no request to obtain new sandboxes.'
|
|
124
|
+
'This is applicable only when the environment enables pooling.'
|
|
105
125
|
)
|
|
106
|
-
] =
|
|
126
|
+
] = 10.0
|
|
107
127
|
|
|
108
128
|
pool_operation_max_parallelism: Annotated[
|
|
109
129
|
int,
|
|
@@ -145,6 +165,7 @@ class BaseEnvironment(interface.Environment):
|
|
|
145
165
|
sandbox_id: str,
|
|
146
166
|
reusable: bool,
|
|
147
167
|
proactive_session_setup: bool,
|
|
168
|
+
keepalive_interval: float | None,
|
|
148
169
|
) -> base_sandbox.BaseSandbox:
|
|
149
170
|
"""Creates a sandbox with the given identifier.
|
|
150
171
|
|
|
@@ -153,6 +174,8 @@ class BaseEnvironment(interface.Environment):
|
|
|
153
174
|
reusable: Whether the sandbox is reusable across user sessions.
|
|
154
175
|
proactive_session_setup: Whether the sandbox performs session setup work
|
|
155
176
|
before a user session is started.
|
|
177
|
+
keepalive_interval: Interval to ping the sandbox for keeping it alive.
|
|
178
|
+
If None, the sandbox will not be pinged.
|
|
156
179
|
|
|
157
180
|
Returns:
|
|
158
181
|
The created sandbox.
|
|
@@ -194,6 +217,7 @@ class BaseEnvironment(interface.Environment):
|
|
|
194
217
|
def _start(self) -> None:
|
|
195
218
|
"""Implementation of starting the environment."""
|
|
196
219
|
if self.min_pool_size > 0:
|
|
220
|
+
# Pre-allocate the sandbox pool before usage.
|
|
197
221
|
self._sandbox_pool = [None] * self.min_pool_size
|
|
198
222
|
for i, sandbox, _ in lf.concurrent_map(
|
|
199
223
|
lambda i: self._bring_up_sandbox_with_retry(
|
|
@@ -207,12 +231,15 @@ class BaseEnvironment(interface.Environment):
|
|
|
207
231
|
),
|
|
208
232
|
):
|
|
209
233
|
self._sandbox_pool[i] = sandbox
|
|
234
|
+
|
|
210
235
|
self._next_sandbox_id = len(self._sandbox_pool)
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
236
|
+
|
|
237
|
+
if self.enable_pooling:
|
|
238
|
+
self._housekeep_thread = threading.Thread(
|
|
239
|
+
target=self._housekeep_loop, daemon=True
|
|
240
|
+
)
|
|
241
|
+
self._housekeep_counter = 0
|
|
242
|
+
self._housekeep_thread.start()
|
|
216
243
|
|
|
217
244
|
def _shutdown(self) -> None:
|
|
218
245
|
"""Implementation of shutting down the environment."""
|
|
@@ -256,7 +283,7 @@ class BaseEnvironment(interface.Environment):
|
|
|
256
283
|
@property
|
|
257
284
|
def enable_pooling(self) -> bool:
|
|
258
285
|
"""Returns whether the environment enables pooling."""
|
|
259
|
-
return self.
|
|
286
|
+
return self.max_pool_size > 0
|
|
260
287
|
|
|
261
288
|
@property
|
|
262
289
|
def status(self) -> interface.Environment.Status:
|
|
@@ -314,11 +341,6 @@ class BaseEnvironment(interface.Environment):
|
|
|
314
341
|
self._start_time = time.time()
|
|
315
342
|
self._set_status(self.Status.ONLINE)
|
|
316
343
|
self.on_start(duration=time.time() - starting_time)
|
|
317
|
-
|
|
318
|
-
pg.logging.info(
|
|
319
|
-
'[%s]: %s started in %.2f seconds.',
|
|
320
|
-
self.id, self.__class__.__name__, time.time() - starting_time
|
|
321
|
-
)
|
|
322
344
|
except BaseException as e:
|
|
323
345
|
self.on_start(duration=time.time() - starting_time, error=e)
|
|
324
346
|
self.shutdown()
|
|
@@ -338,13 +360,8 @@ class BaseEnvironment(interface.Environment):
|
|
|
338
360
|
self._set_status(self.Status.SHUTTING_DOWN)
|
|
339
361
|
|
|
340
362
|
try:
|
|
341
|
-
|
|
342
|
-
self._shutdown()
|
|
363
|
+
self._shutdown()
|
|
343
364
|
self.on_shutdown()
|
|
344
|
-
pg.logging.info(
|
|
345
|
-
'[%s]: %s shutdown in %.2f seconds.',
|
|
346
|
-
self.id, self.__class__.__name__, t.elapse
|
|
347
|
-
)
|
|
348
365
|
except BaseException as e: # pylint: disable=broad-except
|
|
349
366
|
self.on_shutdown(error=e)
|
|
350
367
|
raise e
|
|
@@ -416,6 +433,7 @@ class BaseEnvironment(interface.Environment):
|
|
|
416
433
|
sandbox_id=sandbox_id,
|
|
417
434
|
reusable=self.enable_pooling,
|
|
418
435
|
proactive_session_setup=self.proactive_session_setup,
|
|
436
|
+
keepalive_interval=self.sandbox_keepalive_interval,
|
|
419
437
|
)
|
|
420
438
|
for handler in self.event_handlers:
|
|
421
439
|
sandbox.add_event_handler(handler)
|
|
@@ -482,19 +500,8 @@ class BaseEnvironment(interface.Environment):
|
|
|
482
500
|
|
|
483
501
|
def _housekeep_loop(self) -> None:
|
|
484
502
|
"""Housekeeping loop for the environment."""
|
|
485
|
-
pg.logging.info(
|
|
486
|
-
'[%s]: %s maintenance thread started.', self.id, self.__class__.__name__
|
|
487
|
-
)
|
|
488
|
-
stats_report_time = time.time()
|
|
489
503
|
while self._status not in (self.Status.SHUTTING_DOWN, self.Status.OFFLINE):
|
|
490
504
|
housekeep_start_time = time.time()
|
|
491
|
-
if time.time() - stats_report_time > self.stats_report_interval:
|
|
492
|
-
pg.logging.info(
|
|
493
|
-
'[%s] %s stats: %s.',
|
|
494
|
-
self.id, self.__class__.__name__, self.stats()
|
|
495
|
-
)
|
|
496
|
-
stats_report_time = time.time()
|
|
497
|
-
|
|
498
505
|
dead_pool_indices = [
|
|
499
506
|
i for i, s in enumerate(self._sandbox_pool)
|
|
500
507
|
if s.status == interface.Sandbox.Status.OFFLINE
|
|
@@ -514,7 +521,7 @@ class BaseEnvironment(interface.Environment):
|
|
|
514
521
|
break
|
|
515
522
|
self._housekeep_counter += 1
|
|
516
523
|
self.on_housekeep(time.time() - housekeep_start_time)
|
|
517
|
-
time.sleep(
|
|
524
|
+
time.sleep(self.housekeep_interval)
|
|
518
525
|
|
|
519
526
|
def _replace_dead_sandboxes(self, dead_pool_indices: list[int]) -> bool:
|
|
520
527
|
"""Replaces a dead sandbox with a new one.
|
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 os
|
|
26
27
|
import time
|
|
27
28
|
from typing import Annotated, Callable
|
|
28
29
|
|
|
@@ -106,6 +107,14 @@ class BaseFeature(interface.Feature):
|
|
|
106
107
|
assert self._sandbox is not None, 'Feature has not been set up yet.'
|
|
107
108
|
return self._sandbox
|
|
108
109
|
|
|
110
|
+
@property
|
|
111
|
+
def working_dir(self) -> str | None:
|
|
112
|
+
"""Returns the working directory of the feature."""
|
|
113
|
+
sandbox_workdir = self.sandbox.working_dir
|
|
114
|
+
if sandbox_workdir is None:
|
|
115
|
+
return None
|
|
116
|
+
return os.path.join(sandbox_workdir, self.name)
|
|
117
|
+
|
|
109
118
|
#
|
|
110
119
|
# Setup and teardown of the feature.
|
|
111
120
|
#
|
|
@@ -204,7 +213,7 @@ class BaseFeature(interface.Feature):
|
|
|
204
213
|
self,
|
|
205
214
|
name: str,
|
|
206
215
|
duration: float,
|
|
207
|
-
error: BaseException | None,
|
|
216
|
+
error: BaseException | None = None,
|
|
208
217
|
**kwargs
|
|
209
218
|
) -> None:
|
|
210
219
|
"""Called when a sandbox activity is performed."""
|
langfun/env/base_sandbox.py
CHANGED
|
@@ -29,6 +29,7 @@ import time
|
|
|
29
29
|
from typing import Annotated, Any, Callable, Iterator, Sequence, Type
|
|
30
30
|
|
|
31
31
|
from langfun.env import interface
|
|
32
|
+
from langfun.env.event_handlers import base as event_handler_base
|
|
32
33
|
import pyglove as pg
|
|
33
34
|
|
|
34
35
|
|
|
@@ -36,7 +37,7 @@ class BaseSandbox(interface.Sandbox):
|
|
|
36
37
|
"""Base class for a sandbox."""
|
|
37
38
|
|
|
38
39
|
id: Annotated[
|
|
39
|
-
interface.
|
|
40
|
+
interface.Sandbox.Id,
|
|
40
41
|
'The identifier for the sandbox.'
|
|
41
42
|
]
|
|
42
43
|
|
|
@@ -242,14 +243,14 @@ class BaseSandbox(interface.Sandbox):
|
|
|
242
243
|
|
|
243
244
|
def add_event_handler(
|
|
244
245
|
self,
|
|
245
|
-
event_handler:
|
|
246
|
+
event_handler: event_handler_base.EventHandler | None
|
|
246
247
|
) -> None:
|
|
247
248
|
"""Sets the event handler for the sandbox."""
|
|
248
249
|
self._event_handlers.append(event_handler)
|
|
249
250
|
|
|
250
251
|
def remove_event_handler(
|
|
251
252
|
self,
|
|
252
|
-
event_handler:
|
|
253
|
+
event_handler: event_handler_base.EventHandler | None
|
|
253
254
|
) -> None:
|
|
254
255
|
"""Removes the event handler for the sandbox."""
|
|
255
256
|
self._event_handlers.remove(event_handler)
|
|
@@ -357,10 +358,6 @@ class BaseSandbox(interface.Sandbox):
|
|
|
357
358
|
|
|
358
359
|
duration = time.time() - starting_time
|
|
359
360
|
self.on_start(duration)
|
|
360
|
-
pg.logging.info(
|
|
361
|
-
'[%s]: Sandbox started in %.2f seconds.',
|
|
362
|
-
self.id, duration
|
|
363
|
-
)
|
|
364
361
|
except BaseException as e: # pylint: disable=broad-except
|
|
365
362
|
duration = time.time() - starting_time
|
|
366
363
|
pg.logging.error(
|
|
@@ -422,7 +419,6 @@ class BaseSandbox(interface.Sandbox):
|
|
|
422
419
|
return
|
|
423
420
|
|
|
424
421
|
self._set_status(interface.Sandbox.Status.SHUTTING_DOWN)
|
|
425
|
-
shutdown_start_time = time.time()
|
|
426
422
|
|
|
427
423
|
if (self._housekeep_thread is not None
|
|
428
424
|
and threading.current_thread() is not self._housekeep_thread):
|
|
@@ -433,15 +429,6 @@ class BaseSandbox(interface.Sandbox):
|
|
|
433
429
|
try:
|
|
434
430
|
self._shutdown()
|
|
435
431
|
self._set_status(interface.Sandbox.Status.OFFLINE)
|
|
436
|
-
|
|
437
|
-
pg.logging.info(
|
|
438
|
-
'[%s]: Sandbox shutdown in %.2f seconds. '
|
|
439
|
-
'(lifetime: %.2f seconds, teardown errors: %s)',
|
|
440
|
-
self.id,
|
|
441
|
-
time.time() - shutdown_start_time,
|
|
442
|
-
time.time() - self._start_time if self._start_time else 0,
|
|
443
|
-
teardown_error
|
|
444
|
-
)
|
|
445
432
|
self.on_shutdown(teardown_error)
|
|
446
433
|
shutdown_error = None
|
|
447
434
|
except BaseException as e: # pylint: disable=broad-except
|
|
@@ -656,14 +643,6 @@ class BaseSandbox(interface.Sandbox):
|
|
|
656
643
|
self._set_status(interface.Sandbox.Status.ACQUIRED)
|
|
657
644
|
shutdown_sandbox = True
|
|
658
645
|
|
|
659
|
-
pg.logging.info(
|
|
660
|
-
'[%s]: User session %s ended. '
|
|
661
|
-
'(lifetime: %.2f seconds, teardown errors: %s).',
|
|
662
|
-
self.id,
|
|
663
|
-
self._session_id,
|
|
664
|
-
time.time() - self._session_start_time,
|
|
665
|
-
end_session_error
|
|
666
|
-
)
|
|
667
646
|
self._session_start_time = None
|
|
668
647
|
self._session_event_handler = None
|
|
669
648
|
|
|
@@ -687,9 +666,31 @@ class BaseSandbox(interface.Sandbox):
|
|
|
687
666
|
last_ping = now
|
|
688
667
|
last_housekeep_time = {name: now for name in self._features.keys()}
|
|
689
668
|
|
|
669
|
+
def _next_housekeep_wait_time() -> float:
|
|
670
|
+
# Decide how long to sleep for the next housekeeping.
|
|
671
|
+
next_housekeep_time = None
|
|
672
|
+
if self.keepalive_interval is not None:
|
|
673
|
+
next_housekeep_time = last_ping + self.keepalive_interval
|
|
674
|
+
|
|
675
|
+
for name, feature in self._features.items():
|
|
676
|
+
if feature.housekeep_interval is None:
|
|
677
|
+
continue
|
|
678
|
+
next_feature_housekeep_time = (
|
|
679
|
+
last_housekeep_time[name] + feature.housekeep_interval
|
|
680
|
+
)
|
|
681
|
+
if (next_housekeep_time is None
|
|
682
|
+
or next_housekeep_time > next_feature_housekeep_time):
|
|
683
|
+
next_housekeep_time = next_feature_housekeep_time
|
|
684
|
+
|
|
685
|
+
# Housekeep loop is installed when at least one feature requires
|
|
686
|
+
# housekeeping or the sandbox has a keepalive interval.
|
|
687
|
+
assert next_housekeep_time is not None
|
|
688
|
+
return max(0, next_housekeep_time - time.time())
|
|
689
|
+
|
|
690
690
|
while self._status not in (self.Status.SHUTTING_DOWN, self.Status.OFFLINE):
|
|
691
691
|
housekeep_start = time.time()
|
|
692
692
|
if self.keepalive_interval is not None:
|
|
693
|
+
|
|
693
694
|
if time.time() - last_ping > self.keepalive_interval:
|
|
694
695
|
try:
|
|
695
696
|
self.ping()
|
|
@@ -731,7 +732,7 @@ class BaseSandbox(interface.Sandbox):
|
|
|
731
732
|
|
|
732
733
|
self._housekeep_counter += 1
|
|
733
734
|
self.on_housekeep(time.time() - housekeep_start)
|
|
734
|
-
time.sleep(
|
|
735
|
+
time.sleep(_next_housekeep_wait_time())
|
|
735
736
|
|
|
736
737
|
#
|
|
737
738
|
# Event handlers subclasses can override.
|