Skip to content

Commit 9c078a3

Browse files
committed
WIP: WTF
1 parent 8c2748e commit 9c078a3

File tree

2 files changed

+44
-2
lines changed

2 files changed

+44
-2
lines changed

src/appose/types.py

+27-2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import json
3131
import re
3232
from math import ceil, prod
33+
from multiprocessing import resource_tracker
3334
from multiprocessing.shared_memory import SharedMemory
3435
from typing import Any, Dict, Sequence, Union
3536

@@ -110,11 +111,35 @@ def default(self, obj):
110111
}
111112
return super().default(obj)
112113

113-
114+
foo = True
114115
def _appose_object_hook(obj: Dict):
115116
atype = obj.get("appose_type")
116117
if atype == "shm":
117-
return SharedMemory(name=(obj["name"]), size=(obj["size"]))
118+
# Attach to existing shared memory block.
119+
shm = SharedMemory(name=(obj["name"]), size=(obj["size"]))
120+
121+
# HACK: Work around the Python resource trackers's vigorous effort to
122+
# garbage collect all shared memory blocks after all known references
123+
# are done using them. Unfortunately, due to how Appose invokes Python
124+
# worker processes, the resource tracker does not know about the
125+
# reference from the service process, and overeagerly eats the memory
126+
# when the worker shuts down. To avoid this issue, we tell the worker
127+
# process's associated resource tracker to ignore this particular
128+
# shared memory block, instead trusting the process that actually
129+
# created it to clean up when finished.
130+
#
131+
# This logic could go wrong if the worker process creates a
132+
# SharedMemory, returns it to the service process as an output, and
133+
# then the service process subsequently passes it back to the worker as
134+
# an input argument: such a sequence of events would lead to the named
135+
# shared memory in question being unregistered with the resource
136+
# tracker here, even though it in fact *was* this worker process that
137+
# created the shared memory block earlier... but I don't have a clear
138+
# idea for how to avoid this difficulty at the moment.
139+
name = "/" + shm.name
140+
resource_tracker.unregister(name, "shared_memory")
141+
142+
return shm
118143
elif atype == "ndarray":
119144
return NDArray(obj["dtype"], obj["shape"], obj["shm"])
120145
else:

tests/test_shm.py

+17
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,29 @@
3131
from appose.service import TaskStatus
3232

3333
ndarray_inspect = """
34+
#from multiprocessing import resource_tracker
35+
#resource_tracker.unregister(f"/{data.shm.name}", "shared_memory")
3436
task.outputs["size"] = data.shm.size
3537
task.outputs["dtype"] = data.dtype
3638
task.outputs["shape"] = data.shape
3739
task.outputs["sum"] = sum(v for v in data.shm.buf)
3840
"""
3941

42+
# NB: The unregister line above works around the Python resource tracker's
43+
# vigorous effort to clean up shared memory blocks after all known references
44+
# are done using them. Unfortunately, due to how Appose invokes Python worker
45+
# processes, the resource tracker does not know about the reference from the
46+
# service process, and overeagerly eats the memory when the worker shuts down.
47+
# To avoid this issue, we tell the worker process's associated resource tracker
48+
# to ignore this particular shared memory block, instead trusting the process
49+
# that actually created it to clean up when finished.
50+
#
51+
# This logic could go wrong if the worker process creates a SharedMemory,
52+
# returns it to the service process as an output, and then the service process
53+
# subsequently passes it back to the worker as an input argument: such a
54+
# sequence of events would lead to the named shared memory in question being
55+
# unregistered with the resource tracker in the worker process, even though it
56+
# in fact *was* that worker process that created the shared memory block...
4057

4158
def test_ndarray():
4259
env = appose.system()

0 commit comments

Comments
 (0)