added thread lock to avoid conflicts between GC and parameter#175
added thread lock to avoid conflicts between GC and parameter#175
Conversation
creation/deletion
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #175 +/- ##
===========================================
+ Coverage 80.80% 81.15% +0.35%
===========================================
Files 52 52
Lines 4261 4267 +6
Branches 739 740 +1
===========================================
+ Hits 3443 3463 +20
+ Misses 633 624 -9
+ Partials 185 180 -5
Flags with carried forward coverage won't be shown. Click here to find out more.
|
unfounded fears of "overengineering"...
|
Nooope. Going back to the thread lock. gc.disable() affects the GLOBAL garbage collector state across the whole process (all threads). |
This reverts commit 5d3ea2c.
Did you encounter issues in the application when you tried it out? It is only a (VERY) temporary disabling of GC, so memory buildup shouldn't occur. |
Doesn't matter. This is still a valid issue - we shouldn't do things in the library, which can globally affect behaviour of the code linked to it? |
src/easyscience/global_object/map.py
Outdated
| with self._lock: | ||
| if item_id in self._store.keys(): | ||
| return self._store[item_id] | ||
| raise ValueError('Item not in map.') |
There was a problem hiding this comment.
We should be able to avoid this entire error if we just use normal introspection of the dictionary rather than first get the keys:
if item_id in self_store:
return self._store[item_id]Then we're not iterating over values and thus don't need this (it should also be substantially faster).
There was a problem hiding this comment.
'''
• Key existence vs. value availability:
item_id in self._store checks only if the key is present. If the value has been collected, the key will also be removed, so you won’t get a dangling key.
• Iteration safety:
Iterating over self._store.keys() or items() is safe, but note that the contents may shrink during iteration if garbage collection occurs.
• Direct access:
self._store[item_id] will raise KeyError if the object was collected.
'''
So even with direct query we will get the same issue.
There was a problem hiding this comment.
Actually you might be right.
Querying the dict directly with the KeyError except check should be enough. Let's check this with the test script
There was a problem hiding this comment.
Wait, was that a ChatGPT prompt output? It obviously hallucinates. I literally said that its safe to iterate over self._store.keys(), which is what is causing our error.
There was a problem hiding this comment.
Yes, this is what you get when you take AI verbatim. Some good points but all covered in nonsense.
src/easyscience/global_object/map.py
Outdated
| Uses a threading lock to prevent RuntimeError from WeakValueDictionary. | ||
| """ | ||
| with self._lock: | ||
| return vertex.unique_name in self._store.keys() |
There was a problem hiding this comment.
Again, we can avoid the lock and this issue if we don't call the keys() method. Just use regular inspection.
src/easyscience/global_object/map.py
Outdated
| self.__type_dict[name].type = obj_type | ||
| with self._lock: | ||
| name = obj.unique_name | ||
| if name in self._store.keys(): |
| del self.__type_dict[vertex1][self.__type_dict[vertex1].index(vertex2)] | ||
|
|
||
| def prune(self, key: str): | ||
| if key in self.__type_dict.keys(): |
src/easyscience/global_object/map.py
Outdated
| for vertex in self.vertices(): | ||
| self.prune(vertex) | ||
| gc.collect() | ||
| self.__type_dict = {} |
There was a problem hiding this comment.
Actually, can't we get rid of this method and just use the built-in clear method of a regular dictionary?
|
I really like the retry look implementation for the vertices method. If this works I'd say its' good to merge :) |
Fixed the
RuntimeError: dictionary changed size during iterationissue in the vertices method by implementing thread-safety: iterations handling modifications during garbage collection.This seems to have been a concurrency problem with weak references, where the dictionary is being modified during iteration.
Take the attached script (.txt -> .py cause GitHub)
run_test_suite.txt
Run it in say,
EasyReflectometryLiborcorelibThis now runs fine.
Root Cause
The
Mapclass usesweakref.WeakValueDictionary()to store object references. When:generate_unique_name()is called (e.g., when creating a newParameter)self.map.vertices()which returnslist(self._store.keys()).keys(), the garbage collector may runWeakValueDictionaryRuntimeError: dictionary changed size during iterationThis is particularly likely to happen when:
Lattice.volumeproperty)