thds.mops 3.9.20250929214325__py3-none-any.whl → 3.9.20250930235847__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 thds.mops might be problematic. Click here for more details.
- thds/mops/k8s/uncertain_future.py +32 -22
- thds/mops/k8s/watch.py +8 -3
- {thds_mops-3.9.20250929214325.dist-info → thds_mops-3.9.20250930235847.dist-info}/METADATA +1 -1
- {thds_mops-3.9.20250929214325.dist-info → thds_mops-3.9.20250930235847.dist-info}/RECORD +7 -7
- {thds_mops-3.9.20250929214325.dist-info → thds_mops-3.9.20250930235847.dist-info}/WHEEL +0 -0
- {thds_mops-3.9.20250929214325.dist-info → thds_mops-3.9.20250930235847.dist-info}/entry_points.txt +0 -0
- {thds_mops-3.9.20250929214325.dist-info → thds_mops-3.9.20250930235847.dist-info}/top_level.txt +0 -0
|
@@ -44,20 +44,20 @@ class _FutureInterpretationShim(ty.Generic[R_0, R]):
|
|
|
44
44
|
def __hash__(self) -> int:
|
|
45
45
|
return hash(self._id)
|
|
46
46
|
|
|
47
|
-
def
|
|
47
|
+
def interpret(self, r_0: ty.Optional[R_0], last_seen_at: float) -> ty.Optional[Self]:
|
|
48
48
|
"""First and foremost - this _must_ be treated as an object that the creator
|
|
49
49
|
is ultimately responsible for calling on a semi-regular basis. It represents a
|
|
50
50
|
likely deadlock for the holder of the Future if it is never called.
|
|
51
51
|
|
|
52
|
-
Return
|
|
53
|
-
Return
|
|
52
|
+
Return None if the Future is still in progress and should not be unregistered.
|
|
53
|
+
Return self if the Future is done and should be unregistered.
|
|
54
54
|
"""
|
|
55
55
|
try:
|
|
56
56
|
interpretation = self._interpreter(r_0, last_seen_at)
|
|
57
57
|
if isinstance(interpretation, NotYetDone):
|
|
58
58
|
return None # do nothing and do not unregister - the status is still in progress.
|
|
59
59
|
|
|
60
|
-
self.future.set_result(interpretation)
|
|
60
|
+
self.future.set_result(interpretation) # resolved successfully!
|
|
61
61
|
except Exception as e:
|
|
62
62
|
self.future.set_exception(e)
|
|
63
63
|
|
|
@@ -71,7 +71,7 @@ K = ty.TypeVar("K") # Key type for the UncertainFuturesTracker
|
|
|
71
71
|
class _FuturesState(ty.Generic[R_0]):
|
|
72
72
|
"""Represents a single 'observable' that may have multiple Futures (and therefore interpretations) associated with it."""
|
|
73
73
|
|
|
74
|
-
futshims:
|
|
74
|
+
futshims: set[_FutureInterpretationShim[R_0, ty.Any]]
|
|
75
75
|
last_seen_at: float
|
|
76
76
|
|
|
77
77
|
|
|
@@ -94,10 +94,16 @@ class UncertainFuturesTracker(ty.Generic[K, R_0]):
|
|
|
94
94
|
never resolve the Future, then a caller may be waiting for it forever. Therefore, we
|
|
95
95
|
ask the original requestor of the Future to specify how long they are willing to wait
|
|
96
96
|
to get a result, after which point we will resolve the Future as an exception.
|
|
97
|
+
|
|
98
|
+
Notably, once we have seen an object, we will not ever remove it from our tracking list.
|
|
99
|
+
This implies a certain amount of memory growth over time, but it avoids race conditions
|
|
100
|
+
between producers and consumers of the Futures.
|
|
97
101
|
"""
|
|
98
102
|
|
|
99
103
|
def __init__(self, allowed_stale_seconds: float) -> None:
|
|
100
104
|
self._keyed_futures_state = collections.OrderedDict[K, _FuturesState[R_0]]()
|
|
105
|
+
# ordered from least-recently-seen to most-recently-seen, so that we can easily garbage collect
|
|
106
|
+
# potentially stale Futures, which will be at the front of the OrderedDict.
|
|
101
107
|
self._lock = threading.Lock() # i don't trust ordered dict operations to be thread-safe.
|
|
102
108
|
self._check_stale_seconds = allowed_stale_seconds
|
|
103
109
|
|
|
@@ -105,16 +111,11 @@ class UncertainFuturesTracker(ty.Generic[K, R_0]):
|
|
|
105
111
|
futshim = _FutureInterpretationShim(interpreter)
|
|
106
112
|
with self._lock:
|
|
107
113
|
if key not in self._keyed_futures_state:
|
|
108
|
-
self._keyed_futures_state[key] = _FuturesState(
|
|
109
|
-
|
|
110
|
-
last_seen_at=official_timer() + self._check_stale_seconds,
|
|
111
|
-
# we provide a double margin for objects that we have never seen before.
|
|
112
|
-
)
|
|
113
|
-
self._keyed_futures_state.move_to_end(key, last=False)
|
|
114
|
-
# never seen and therefore should be at the beginning (most stale)
|
|
114
|
+
self._keyed_futures_state[key] = _FuturesState({futshim}, last_seen_at=official_timer())
|
|
115
|
+
# never seen and therefore should be at the end (least stale)
|
|
115
116
|
else:
|
|
116
117
|
# maintain our ordered dict so we can handle garbage collection of stale Futures.
|
|
117
|
-
self._keyed_futures_state[key].futshims.
|
|
118
|
+
self._keyed_futures_state[key].futshims.add(futshim)
|
|
118
119
|
|
|
119
120
|
return futshim.future
|
|
120
121
|
|
|
@@ -126,10 +127,10 @@ class UncertainFuturesTracker(ty.Generic[K, R_0]):
|
|
|
126
127
|
If `key` is None, we will update all Futures that have been created so far.
|
|
127
128
|
"""
|
|
128
129
|
|
|
129
|
-
def
|
|
130
|
+
def interpret_event(fut_state: _FuturesState[R_0], inner_r_0: ty.Optional[R_0]) -> None:
|
|
130
131
|
for future_shim_that_is_done in core.parallel.yield_results(
|
|
131
132
|
[
|
|
132
|
-
core.thunks.thunking(futshim)(inner_r_0, fut_state.last_seen_at)
|
|
133
|
+
core.thunks.thunking(futshim.interpret)(inner_r_0, fut_state.last_seen_at)
|
|
133
134
|
for futshim in fut_state.futshims
|
|
134
135
|
],
|
|
135
136
|
progress_logger=core.log.getLogger(__name__).debug,
|
|
@@ -137,24 +138,33 @@ class UncertainFuturesTracker(ty.Generic[K, R_0]):
|
|
|
137
138
|
):
|
|
138
139
|
if future_shim_that_is_done is not None:
|
|
139
140
|
# the Future is done, so we can remove it from the list of Futures.
|
|
140
|
-
fut_state.futshims.
|
|
141
|
+
fut_state.futshims.discard(future_shim_that_is_done)
|
|
141
142
|
|
|
142
143
|
if key is not None:
|
|
143
144
|
with self._lock:
|
|
144
145
|
if key not in self._keyed_futures_state:
|
|
145
|
-
self._keyed_futures_state[key] = _FuturesState(
|
|
146
|
+
self._keyed_futures_state[key] = _FuturesState(set(), last_seen_at=official_timer())
|
|
146
147
|
else:
|
|
147
148
|
# maintain our ordered dict so we can handle garbage collection of stale Futures.
|
|
148
149
|
self._keyed_futures_state.move_to_end(key)
|
|
149
150
|
self._keyed_futures_state[key].last_seen_at = official_timer()
|
|
150
151
|
|
|
151
|
-
fut_state
|
|
152
|
-
|
|
152
|
+
if fut_state := self._keyed_futures_state.get(key):
|
|
153
|
+
interpret_event(fut_state, r_0)
|
|
153
154
|
|
|
155
|
+
#
|
|
154
156
|
# 'garbage collect' any Futures that haven't been updated in a while.
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
157
|
+
#
|
|
158
|
+
with self._lock:
|
|
159
|
+
safe_futures = list(self._keyed_futures_state.values())
|
|
160
|
+
# this avoids holding the lock, but also avoids RuntimeError: OrderedDict mutated during iteration
|
|
161
|
+
now = official_timer()
|
|
162
|
+
for futs_state in safe_futures:
|
|
163
|
+
if now > futs_state.last_seen_at + self._check_stale_seconds:
|
|
164
|
+
interpret_event(futs_state, None)
|
|
165
|
+
# None means we have no new information about the object.
|
|
166
|
+
# the interpreter must decide what to do with that, plus the last seen time.
|
|
167
|
+
|
|
158
168
|
else: # these are ordered, so once we see one that's not stale, we can stop checking.
|
|
159
169
|
# this prevents us from having to do O(N) checks for every update.
|
|
160
170
|
break
|
thds/mops/k8s/watch.py
CHANGED
|
@@ -112,9 +112,14 @@ def callback_events(
|
|
|
112
112
|
) -> None:
|
|
113
113
|
"""Suitable for use with a daemon thread."""
|
|
114
114
|
for namespace, obj, event in event_yielder:
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
try:
|
|
116
|
+
should_exit = on_event(namespace, obj, event)
|
|
117
|
+
if should_exit:
|
|
118
|
+
break
|
|
119
|
+
except Exception:
|
|
120
|
+
logger.exception(
|
|
121
|
+
"Exception in k8s watch event callback [probably a bug in mops] - continuing..."
|
|
122
|
+
)
|
|
118
123
|
|
|
119
124
|
|
|
120
125
|
def _default_get_name(obj: ty.Any) -> str:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: thds.mops
|
|
3
|
-
Version: 3.9.
|
|
3
|
+
Version: 3.9.20250930235847
|
|
4
4
|
Summary: ML Ops tools for Trilliant Health
|
|
5
5
|
Author-email: Trilliant Health <info@trillianthealth.com>
|
|
6
6
|
Project-URL: Repository, https://github.com/TrilliantHealth/ds-monorepo
|
|
@@ -31,10 +31,10 @@ thds/mops/k8s/namespace.py,sha256=Z6trVTU9WFashto4PqIhTcxu-foOF93W0TpgqCU7WIA,38
|
|
|
31
31
|
thds/mops/k8s/node_selection.py,sha256=Gy2Jz8IxZblg2LmtGg8-MtKI4RmXz2AMXqFPP8OQyu0,2065
|
|
32
32
|
thds/mops/k8s/retry.py,sha256=JVfP304kItpLs5nrONHE5UWkVWlrFGlV_oFQqhq3zHg,2846
|
|
33
33
|
thds/mops/k8s/too_old_resource_version.py,sha256=S7ltVA-LrxUpQ8Q__AB0nQmezN8Mmnx5oKK62_baAKI,1500
|
|
34
|
-
thds/mops/k8s/uncertain_future.py,sha256=
|
|
34
|
+
thds/mops/k8s/uncertain_future.py,sha256=_ix-4EqZE_MY5sbLjT-lQ9GIa7woQ3iunPVtDapIhi8,7752
|
|
35
35
|
thds/mops/k8s/wait_job.py,sha256=_X5lSn-3CE4V-_ra0kF1WtxkAiOgqSom8mU1-0hhMio,2445
|
|
36
36
|
thds/mops/k8s/warn_image_backoff.py,sha256=ls_zLSnRbJjO4ICjq1Rk21EXh190l2dT6nKg-PT8Das,1934
|
|
37
|
-
thds/mops/k8s/watch.py,sha256=
|
|
37
|
+
thds/mops/k8s/watch.py,sha256=nH9HBoRCvt8FRhUav9at71QnTCh0plWga3rp7aUYb1E,14212
|
|
38
38
|
thds/mops/k8s/tools/krsync.py,sha256=us7pXX0-bRMwD2oAno7Z6BJcPs6FgaUabHW0STyQJYg,1773
|
|
39
39
|
thds/mops/k8s/tools/krsync.sh,sha256=fWgwkdzWnJeTbzEA_uBiIIi-bNU4nXAYj3dNovyRluU,747
|
|
40
40
|
thds/mops/pure/__init__.py,sha256=3xLimQ2JWdeq1YgPs7bPwlwOspzPRwaR2w2KX7vfJU0,1624
|
|
@@ -109,8 +109,8 @@ thds/mops/pure/tools/summarize/cli.py,sha256=7kDtn24ok8oBO3jFjlMmOK3jnZYpMoE_5Y8
|
|
|
109
109
|
thds/mops/pure/tools/summarize/run_summary.py,sha256=w45qiQr7elrHDiK9Hgs85gtU3gwLuXa447ih1Y23BBY,5776
|
|
110
110
|
thds/mops/testing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
111
111
|
thds/mops/testing/deferred_imports.py,sha256=f0ezCgQAtzTqW1yAOb0OWgsB9ZrlztLB894LtpWDaVw,3780
|
|
112
|
-
thds_mops-3.9.
|
|
113
|
-
thds_mops-3.9.
|
|
114
|
-
thds_mops-3.9.
|
|
115
|
-
thds_mops-3.9.
|
|
116
|
-
thds_mops-3.9.
|
|
112
|
+
thds_mops-3.9.20250930235847.dist-info/METADATA,sha256=lZx15ZxrydRhLaSQMO7KnUyxnUoVzOAqBtgQSN1bgyY,2225
|
|
113
|
+
thds_mops-3.9.20250930235847.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
114
|
+
thds_mops-3.9.20250930235847.dist-info/entry_points.txt,sha256=qKvCAaB80syXfxVR3xx6x9J0YJdaQWkIbVSw-NwFgMw,322
|
|
115
|
+
thds_mops-3.9.20250930235847.dist-info/top_level.txt,sha256=LTZaE5SkWJwv9bwOlMbIhiS-JWQEEIcjVYnJrt-CriY,5
|
|
116
|
+
thds_mops-3.9.20250930235847.dist-info/RECORD,,
|
|
File without changes
|
{thds_mops-3.9.20250929214325.dist-info → thds_mops-3.9.20250930235847.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{thds_mops-3.9.20250929214325.dist-info → thds_mops-3.9.20250930235847.dist-info}/top_level.txt
RENAMED
|
File without changes
|