1515# this resource tracker process, "killall python" would probably leave unlinked
1616# resources.
1717
18+ import base64
1819import os
1920import signal
2021import sys
2122import threading
2223import warnings
2324from collections import deque
2425
26+ import json
27+
2528from . import spawn
2629from . import util
2730
@@ -196,6 +199,17 @@ def _launch(self):
196199 finally :
197200 os .close (r )
198201
202+ def _make_probe_message (self ):
203+ """Return a JSON-encoded probe message."""
204+ return (
205+ json .dumps (
206+ {"cmd" : "PROBE" , "rtype" : "noop" },
207+ ensure_ascii = True ,
208+ separators = ("," , ":" ),
209+ )
210+ + "\n "
211+ ).encode ("ascii" )
212+
199213 def _ensure_running_and_write (self , msg = None ):
200214 with self ._lock :
201215 if self ._lock ._recursion_count () > 1 :
@@ -207,7 +221,7 @@ def _ensure_running_and_write(self, msg=None):
207221 if self ._fd is not None :
208222 # resource tracker was launched before, is it still running?
209223 if msg is None :
210- to_send = b'PROBE:0:noop \n '
224+ to_send = self . _make_probe_message ()
211225 else :
212226 to_send = msg
213227 try :
@@ -234,7 +248,7 @@ def _check_alive(self):
234248 try :
235249 # We cannot use send here as it calls ensure_running, creating
236250 # a cycle.
237- os .write (self ._fd , b'PROBE:0:noop \n ' )
251+ os .write (self ._fd , self . _make_probe_message () )
238252 except OSError :
239253 return False
240254 else :
@@ -253,11 +267,25 @@ def _write(self, msg):
253267 assert nbytes == len (msg ), f"{ nbytes = } != { len (msg )= } "
254268
255269 def _send (self , cmd , name , rtype ):
256- msg = f"{ cmd } :{ name } :{ rtype } \n " .encode ("ascii" )
257- if len (msg ) > 512 :
258- # posix guarantees that writes to a pipe of less than PIPE_BUF
259- # bytes are atomic, and that PIPE_BUF >= 512
260- raise ValueError ('msg too long' )
270+ # POSIX guarantees that writes to a pipe of less than PIPE_BUF (512 on Linux)
271+ # bytes are atomic. Therefore, we want the message to be shorter than 512 bytes.
272+ # POSIX shm_open() and sem_open() require the name, including its leading slash,
273+ # to be at most NAME_MAX bytes (255 on Linux)
274+ # With json.dump(..., ensure_ascii=True) every non-ASCII byte becomes a 6-char
275+ # escape like \uDC80.
276+ # As we want the overall message to be kept atomic and therefore smaller than 512,
277+ # we encode encode the raw name bytes with URL-safe Base64 - so a 255 long name
278+ # will not exceed 340 bytes.
279+ b = name .encode ('utf-8' , 'surrogateescape' )
280+ if len (b ) > 255 :
281+ raise ValueError ('shared memory name too long (max 255 bytes)' )
282+ b64 = base64 .urlsafe_b64encode (b ).decode ('ascii' )
283+
284+ payload = {"cmd" : cmd , "rtype" : rtype , "base64_name" : b64 }
285+ msg = (json .dumps (payload , ensure_ascii = True , separators = ("," , ":" )) + "\n " ).encode ("ascii" )
286+
287+ # The entire JSON message is guaranteed < PIPE_BUF (512 bytes) by construction.
288+ assert len (msg ) <= 512 , f"internal error: message too long ({ len (msg )} bytes)"
261289
262290 self ._ensure_running_and_write (msg )
263291
@@ -290,7 +318,23 @@ def main(fd):
290318 with open (fd , 'rb' ) as f :
291319 for line in f :
292320 try :
293- cmd , name , rtype = line .strip ().decode ('ascii' ).split (':' )
321+ try :
322+ obj = json .loads (line .decode ('ascii' ))
323+ except Exception as e :
324+ raise ValueError ("malformed resource_tracker message: %r" % (line ,)) from e
325+
326+ cmd = obj ["cmd" ]
327+ rtype = obj ["rtype" ]
328+ b64 = obj .get ("base64_name" , "" )
329+
330+ if not isinstance (cmd , str ) or not isinstance (rtype , str ) or not isinstance (b64 , str ):
331+ raise ValueError ("malformed resource_tracker fields: %r" % (obj ,))
332+
333+ try :
334+ name = base64 .urlsafe_b64decode (b64 ).decode ('utf-8' , 'surrogateescape' )
335+ except ValueError as e :
336+ raise ValueError ("malformed resource_tracker base64_name: %r" % (b64 ,)) from e
337+
294338 cleanup_func = _CLEANUP_FUNCS .get (rtype , None )
295339 if cleanup_func is None :
296340 raise ValueError (
0 commit comments