Skip to content

Commit

Permalink
Merge pull request micro-manager#688 from henrypinkard/main
Browse files Browse the repository at this point in the history
prevent process from hanging + bridge logic cleanup that shouldn't affect functionality
  • Loading branch information
henrypinkard authored Sep 7, 2023
2 parents 4121e08 + 5b11bbb commit b4ce051
Show file tree
Hide file tree
Showing 3 changed files with 15 additions and 40 deletions.
2 changes: 1 addition & 1 deletion pycromanager/_version.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
version_info = (0, 29, 0)
version_info = (0, 29, 1)
__version__ = ".".join(map(str, version_info))
6 changes: 6 additions & 0 deletions pycromanager/acquisition/java_backend_acquisitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ def _notification_handler_fn(acquisition, notification_push_port, connected_even
finally:
monitor_socket.close()


class JavaBackendAcquisition(Acquisition, metaclass=NumpyDocstringInheritanceMeta):
"""
Pycro-Manager acquisition that uses a Java runtime backend via a ZeroMQ communication layer.
Expand Down Expand Up @@ -356,6 +357,8 @@ def await_completion(self):
if hasattr(self, '_event_thread'):
self._event_thread.join()

# need to do this so its _Bridge can be garbage collected and a reference to the JavaBackendAcquisition
# does not prevent Bridge cleanup and process exiting
self._remote_acq = None

# Wait on all the other threads to shut down properly
Expand All @@ -366,6 +369,9 @@ def await_completion(self):
# for backwards compatiblitiy with older versions of Pycromanager java before this added
self._acq_notification_recieving_thread.join()
self._remote_notification_handler.notification_handling_complete()
# need to do this so its _Bridge can be garbage collected and a reference to the JavaBackendAcquisition
# does not prevent Bridge cleanup and process exiting
self._remote_notification_handler = None
self._acq_notification_dispatcher_thread.join()

self._acq = None
Expand Down
47 changes: 8 additions & 39 deletions pycromanager/zmq_bridge/_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,35 +246,11 @@ def create_or_get_existing_bridge(port: int=DEFAULT_PORT, convert_camel_case: bo
if debug:
print("DEBUG: creating new beidge for port {} thread {}".format(
port, threading.current_thread().name))
return _Bridge(port, convert_camel_case, debug, ip_address, timeout, iterate)


# def __new__(cls, port: int=DEFAULT_PORT, timeout: int=DEFAULT_TIMEOUT, convert_camel_case: bool=True,
# debug: bool=False, *args, **kwargs):
# """
# Only one instance of Bridge per a thread/port combo
# """
# # synchronize this method so multiple threads don't try to create a bridge at the same time
# with _Bridge._bridge_creation_lock:
# thread_id = threading.current_thread().ident
# port_thread_id = (port, thread_id)
#
# # return the existing cached bridge if it exists, otherwise make a new one
# if port_thread_id in _Bridge._cached_bridges_by_port_and_thread.keys():
# bridge = _Bridge._cached_bridges_by_port_and_thread[port_thread_id]()
# if bridge is None:
# raise Exception("Bridge for port {} and thread {} has been "
# "closed but not removed".format(port, threading.current_thread().name))
# if debug:
# print("DEBUG: returning cached bridge for port {} thread {}".format(
# port, threading.current_thread().name))
# return bridge
# else:
# if debug:
# print("DEBUG: creating new beidge for port {} thread {}".format(
# port, threading.current_thread().name))
# return super(_Bridge, cls).__new__(cls)

b = _Bridge(port, convert_camel_case, debug, ip_address, timeout, iterate)
# store weak refs so that the existence of thread/port bridge caching doesn't prevent
# the garbage collection of unused bridge objects
_Bridge._cached_bridges_by_port_and_thread[port_thread_id] = weakref.ref(b)
return b

def __init__(
self, port: int=DEFAULT_PORT, convert_camel_case: bool=True,
Expand All @@ -297,14 +273,7 @@ def __init__(
"""
thread_id = threading.current_thread().ident
port_thread_id = (port, thread_id)
# if port_thread_id in _Bridge._cached_bridges_by_port_and_thread.keys():
# return # already initialized
self._port_thread_id = port_thread_id
# store weak refs so that the existence of thread/port bridge caching doesn't prevent
# the garbage collection of unused bridge objects
self._weak_self_ref = weakref.ref(self)
_Bridge._cached_bridges_by_port_and_thread[port_thread_id] = self._weak_self_ref

self._ip_address = ip_address
self.port = port
self._convert_camel_case = convert_camel_case
Expand Down Expand Up @@ -571,9 +540,9 @@ def __init__(self, serialized_object, bridge: _Bridge):
self._iterate = bridge._iterate

self._closed = False
# In case there's an exception rather than normal garbage collection,
# this makes sure cleanup occurs properly
# Need to use a wr to ensure that reference to close doesnt cause memeory leak
# # In case there's an exception rather than normal garbage collection,
# # this makes sure cleanup occurs properly
# # Need to use a wr to ensure that reference to close doesnt cause memeory leak
wr = weakref.ref(self._close)
def cleanup():
if wr() is not None:
Expand Down

0 comments on commit b4ce051

Please sign in to comment.