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
@@ -200,6 +203,17 @@ def _launch(self):
200203 finally :
201204 os .close (r )
202205
206+ def _make_probe_message (self ):
207+ """Return a JSON-encoded probe message."""
208+ return (
209+ json .dumps (
210+ {"cmd" : "PROBE" , "rtype" : "noop" },
211+ ensure_ascii = True ,
212+ separators = ("," , ":" ),
213+ )
214+ + "\n "
215+ ).encode ("ascii" )
216+
203217 def _ensure_running_and_write (self , msg = None ):
204218 with self ._lock :
205219 if self ._lock ._recursion_count () > 1 :
@@ -211,7 +225,7 @@ def _ensure_running_and_write(self, msg=None):
211225 if self ._fd is not None :
212226 # resource tracker was launched before, is it still running?
213227 if msg is None :
214- to_send = b'PROBE:0:noop \n '
228+ to_send = self . _make_probe_message ()
215229 else :
216230 to_send = msg
217231 try :
@@ -238,7 +252,7 @@ def _check_alive(self):
238252 try :
239253 # We cannot use send here as it calls ensure_running, creating
240254 # a cycle.
241- os .write (self ._fd , b'PROBE:0:noop \n ' )
255+ os .write (self ._fd , self . _make_probe_message () )
242256 except OSError :
243257 return False
244258 else :
@@ -257,11 +271,25 @@ def _write(self, msg):
257271 assert nbytes == len (msg ), f"{ nbytes = } != { len (msg )= } "
258272
259273 def _send (self , cmd , name , rtype ):
260- msg = f"{ cmd } :{ name } :{ rtype } \n " .encode ("ascii" )
261- if len (msg ) > 512 :
262- # posix guarantees that writes to a pipe of less than PIPE_BUF
263- # bytes are atomic, and that PIPE_BUF >= 512
264- raise ValueError ('msg too long' )
274+ # POSIX guarantees that writes to a pipe of less than PIPE_BUF (512 on Linux)
275+ # bytes are atomic. Therefore, we want the message to be shorter than 512 bytes.
276+ # POSIX shm_open() and sem_open() require the name, including its leading slash,
277+ # to be at most NAME_MAX bytes (255 on Linux)
278+ # With json.dump(..., ensure_ascii=True) every non-ASCII byte becomes a 6-char
279+ # escape like \uDC80.
280+ # As we want the overall message to be kept atomic and therefore smaller than 512,
281+ # we encode encode the raw name bytes with URL-safe Base64 - so a 255 long name
282+ # will not exceed 340 bytes.
283+ b = name .encode ('utf-8' , 'surrogateescape' )
284+ if len (b ) > 255 :
285+ raise ValueError ('shared memory name too long (max 255 bytes)' )
286+ b64 = base64 .urlsafe_b64encode (b ).decode ('ascii' )
287+
288+ payload = {"cmd" : cmd , "rtype" : rtype , "base64_name" : b64 }
289+ msg = (json .dumps (payload , ensure_ascii = True , separators = ("," , ":" )) + "\n " ).encode ("ascii" )
290+
291+ # The entire JSON message is guaranteed < PIPE_BUF (512 bytes) by construction.
292+ assert len (msg ) <= 512 , f"internal error: message too long ({ len (msg )} bytes)"
265293
266294 self ._ensure_running_and_write (msg )
267295
@@ -294,7 +322,23 @@ def main(fd):
294322 with open (fd , 'rb' ) as f :
295323 for line in f :
296324 try :
297- cmd , name , rtype = line .strip ().decode ('ascii' ).split (':' )
325+ try :
326+ obj = json .loads (line .decode ('ascii' ))
327+ except Exception as e :
328+ raise ValueError ("malformed resource_tracker message: %r" % (line ,)) from e
329+
330+ cmd = obj ["cmd" ]
331+ rtype = obj ["rtype" ]
332+ b64 = obj .get ("base64_name" , "" )
333+
334+ if not isinstance (cmd , str ) or not isinstance (rtype , str ) or not isinstance (b64 , str ):
335+ raise ValueError ("malformed resource_tracker fields: %r" % (obj ,))
336+
337+ try :
338+ name = base64 .urlsafe_b64decode (b64 ).decode ('utf-8' , 'surrogateescape' )
339+ except ValueError as e :
340+ raise ValueError ("malformed resource_tracker base64_name: %r" % (b64 ,)) from e
341+
298342 cleanup_func = _CLEANUP_FUNCS .get (rtype , None )
299343 if cleanup_func is None :
300344 raise ValueError (
0 commit comments