Skip to content

Commit aab7365

Browse files
authored
Malloc/memleak fixes (#169)
Bugfixes: - Update how finalize and dispose works - Avoid exception due to disposing dictionary twice when it is linked to the same pointer - Fix 'contains' implementation for netdict - Fix incorrect scalar state callback value for on_flushed event - Fix statevalue leaks Other: - Use super().__init__ instaed of <classname>.__init__
1 parent c65962c commit aab7365

File tree

28 files changed

+232
-80
lines changed

28 files changed

+232
-80
lines changed

src/InteropGenerator/Quix.InteropGenerator/Writers/CsharpInteropWriter/InteropHelpers/ExternalTypes/System/Dictionary.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,39 @@ public static void SetValue(IntPtr dictionaryHPtr, IntPtr keyHPtr, IntPtr valHPt
239239
}
240240
}
241241

242+
[UnmanagedCallersOnly(EntryPoint = "dictionary_contains")]
243+
public static bool Contains(IntPtr dictionaryHPtr, IntPtr keyPtr)
244+
{
245+
object key = null;
246+
Type keyType = null;
247+
Type dictType = null;
248+
try
249+
{
250+
var target = InteropUtils.FromHPtr<IDictionary>(dictionaryHPtr);
251+
var targetType = target.GetType();
252+
dictType = (targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof(IDictionary<,>))
253+
? targetType
254+
: targetType.GetInterfaces().FirstOrDefault(y => y.IsGenericType && y.GetGenericTypeDefinition() == typeof(IDictionary<,>));
255+
if (dictType == null)
256+
{
257+
throw new InvalidOperationException($"Type {targetType} does not implement dictionary with types nor is a dictionary with types");
258+
}
259+
keyType = dictType.GetGenericArguments()[0];
260+
261+
key = InteropUtils.PtrToObject(keyPtr, keyType);
262+
263+
return target.Contains(key);
264+
}
265+
catch (Exception ex)
266+
{
267+
InteropUtils.LogDebug("Exception in dictionary_get_value");
268+
InteropUtils.LogDebug($"Arg dictionaryHPtr (IntPtr) has value: {dictionaryHPtr}, converted to dict type {dictType}");
269+
InteropUtils.LogDebug($"Arg keyPtr (IntPtr) has value: {key}, type {keyType}, converted {key}");
270+
InteropUtils.RaiseException(ex);
271+
return default;
272+
}
273+
}
274+
242275
[UnmanagedCallersOnly(EntryPoint = "dictionary_get_value")]
243276
public static IntPtr GetValue(IntPtr dictionaryHPtr, IntPtr keyHPtr)
244277
{

src/InteropGenerator/Quix.InteropGenerator/Writers/PythonWrapperWriter/InteropHelpers/ExternalTypes/System/Dictionary.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,14 @@ def SetValue(dictionary_hptr: c_void_p, key_hptr: c_void_p, value_hptr: c_void_p
212212
def GetValue(dictionary_hptr: c_void_p, key_hptr: c_void_p) -> Any:
213213
ptr = InteropUtils.invoke("dictionary_get_value", dictionary_hptr, key_hptr)
214214
return c_void_p(ptr) if ptr is not None else None
215+
216+
# ctypes function return type//parameter fix
217+
interop_func = InteropUtils.get_function("dictionary_contains")
218+
interop_func.argtypes = [c_void_p, c_void_p]
219+
interop_func.restype = c_bool
220+
@staticmethod
221+
def Contains(dictionary_hptr: c_void_p, key_hptr: c_void_p) -> Any:
222+
return InteropUtils.invoke("dictionary_contains", dictionary_hptr, key_hptr)
215223

216224
# ctypes function return type//parameter fix
217225
interop_func = InteropUtils.get_function("dictionary_get_keys")

src/PythonClient/setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import re
66
import fileinput
77

8-
package_version = "0.5.5.dev6"
8+
package_version = "0.5.5.dev11"
99

1010
with open("README.md", "r") as fh:
1111
long_description = fh.read()

src/PythonClient/src/quixstreams/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,5 @@
5959

6060
from .logging import Logging, LogLevel
6161
from .state import *
62+
from .states import *
63+

src/PythonClient/src/quixstreams/helpers/nativedecorator.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,17 @@ def new_init(self, *args, **kwargs):
1818
orig_init(self, *args, **kwargs)
1919

2020
def new_finalizerfunc(self):
21+
"""
22+
Finalizes the underlying object
23+
Finalize is implementation specific but generally de-references the underlying objects which
24+
may or may not result in garbage collection of the de-referenced objects
25+
"""
2126
if self._nativedecorator_finalized:
2227
return
2328

2429
ptr = getattr(self, "_interop").get_interop_ptr__()
2530

26-
InteropUtils.log_debug(f"Finalizing {cls.__name__} ({ptr.value})")
31+
InteropUtils.log_debug(f"Finalizing {cls.__name__} ({ptr.value}) of object {id(self)}")
2732
InteropUtils.log_debug_indent_increment()
2833

2934
self._nativedecorator_finalized = True
@@ -32,7 +37,7 @@ def new_finalizerfunc(self):
3237
getattr(self, "_interop").dispose_ptr__()
3338

3439
InteropUtils.log_debug_indent_decrement()
35-
InteropUtils.log_debug(f"Finalized {cls.__name__} ({ptr.value})")
40+
InteropUtils.log_debug(f"Finalized {cls.__name__} ({ptr.value}) of object {id(self)}")
3641

3742

3843
def new_del(self):
@@ -47,11 +52,16 @@ def new_exit(self, exc_type, exc_val, exc_tb):
4752
new_dispose(self)
4853
return result
4954

50-
def new_dispose(self, *args, **kwargs):
55+
def new_dispose(self, *args, **kwargs) -> None:
56+
"""
57+
Disposes the underlying object in addition to finalizing it
58+
Dispose is implementation specific but it generally frees underlying resources rather than just
59+
de-references
60+
"""
5161

5262
ptr = getattr(self, "_interop").get_interop_ptr__()
5363

54-
InteropUtils.log_debug(f"Disposing {cls.__name__} ({ptr.value})")
64+
InteropUtils.log_debug(f"Disposing {cls.__name__} ({ptr.value}) of object {id(self)}")
5565
InteropUtils.log_debug_indent_increment()
5666

5767
if self._nativedecorator_finalized:
@@ -61,7 +71,7 @@ def new_dispose(self, *args, **kwargs):
6171
new_finalizerfunc(self)
6272

6373
InteropUtils.log_debug_indent_decrement()
64-
InteropUtils.log_debug(f"Disposed {cls.__name__} ({ptr.value})")
74+
InteropUtils.log_debug(f"Disposed {cls.__name__} ({ptr.value}) of object {id(self)}")
6575

6676
cls.__init__ = new_init
6777
cls._finalizerfunc = new_finalizerfunc

src/PythonClient/src/quixstreams/models/netdict.py

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ def _returnsame(val):
1818

1919
def __init__(self, net_pointer, key_converter_to_python=None, key_converter_from_python=None,
2020
val_converter_to_python=None, val_converter_from_python=None,
21-
key_converter_to_python_list=None, val_converter_to_python_list=None):
21+
key_converter_to_python_list=None, val_converter_to_python_list=None,
22+
self_dispose: bool = True):
2223

2324
self._pointer = net_pointer
2425
self._key_converter_to_python = key_converter_to_python
@@ -49,13 +50,16 @@ def __init__(self, net_pointer, key_converter_to_python=None, key_converter_from
4950
converter_to_python=self._val_converter_to_python,
5051
converter_from_python=self._val_converter_from_python)
5152

52-
self._finalizer = weakref.finalize(self, self._finalizerfunc)
53+
if self_dispose:
54+
self._finalizer = weakref.finalize(self, self._finalizerfunc)
55+
else:
56+
self._finalizer = lambda x: None
5357

5458
def _finalizerfunc(self):
5559
self._finalizer.detach()
5660
InteropUtils.free_hptr(self._pointer)
5761

58-
def dispose(self):
62+
def dispose(self) -> None:
5963
self._finalizer()
6064

6165
def _get_actual_key_from(self, key) -> Any:
@@ -75,9 +79,9 @@ def __getitem__(self, key) -> Any:
7579
item = di.GetValue(self._pointer, actual_key)
7680
return self._get_actual_value_to(item)
7781

78-
def __contains__(self, item) -> bool:
79-
actual_item = self._get_actual_value_from(item)
80-
return di.GetValue(self._pointer, actual_item)
82+
def __contains__(self, key) -> bool:
83+
actual_key = self._get_actual_key_from(key)
84+
return di.Contains(self._pointer, actual_key)
8185

8286
def __str__(self) -> str:
8387
text = "{"
@@ -132,16 +136,17 @@ class NetDict(ReadOnlyNetDict):
132136

133137
def __init__(self, net_pointer, key_converter_to_python=None, key_converter_from_python=None,
134138
val_converter_to_python=None, val_converter_from_python=None,
135-
key_converter_to_python_list=None, val_converter_to_python_list=None):
136-
137-
ReadOnlyNetDict.__init__(self,
138-
net_pointer=net_pointer,
139-
key_converter_to_python=key_converter_to_python,
140-
key_converter_from_python=key_converter_from_python,
141-
val_converter_to_python=val_converter_to_python,
142-
val_converter_from_python=val_converter_from_python,
143-
key_converter_to_python_list=key_converter_to_python_list,
144-
val_converter_to_python_list=val_converter_to_python_list)
139+
key_converter_to_python_list=None, val_converter_to_python_list=None,
140+
self_dispose: bool = True):
141+
142+
super().__init__(net_pointer=net_pointer,
143+
key_converter_to_python=key_converter_to_python,
144+
key_converter_from_python=key_converter_from_python,
145+
val_converter_to_python=val_converter_to_python,
146+
val_converter_from_python=val_converter_from_python,
147+
key_converter_to_python_list=key_converter_to_python_list,
148+
val_converter_to_python_list=val_converter_to_python_list,
149+
self_dispose=self_dispose)
145150

146151
@staticmethod
147152
def constructor_for_string_string(net_pointer=None):

src/PythonClient/src/quixstreams/models/netlist.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def _finalizerfunc(self):
2828
self._finalizer.detach()
2929
InteropUtils.free_hptr(self._pointer)
3030

31-
def dispose(self):
31+
def dispose(self) -> None:
3232
self._finalizer()
3333

3434
def _get_actual_from(self, value):
@@ -82,7 +82,7 @@ class NetList(NetReadOnlyList):
8282
"""
8383

8484
def __init__(self, net_pointer, converter_to_python=None, converter_from_python=None):
85-
NetReadOnlyList.__init__(self, net_pointer, converter_to_python, converter_from_python)
85+
super().__init__(net_pointer, converter_to_python, converter_from_python)
8686

8787
@staticmethod
8888
def constructor_for_string(net_pointer=None):

src/PythonClient/src/quixstreams/models/streamconsumer/streampropertiesconsumer.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ def __init__(self, stream_consumer: 'StreamConsumer', net_pointer: ctypes.c_void
4242
self._parents = None
4343

4444
def _finalizerfunc(self):
45+
self._on_changed_dispose()
46+
47+
def dispose(self) -> None:
4548
if self._metadata is not None:
4649
self._metadata.dispose()
4750
if self._parents is not None:

src/PythonClient/src/quixstreams/models/streamconsumer/streamtimeseriesconsumer.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ def __init__(self, stream_consumer, net_pointer: ctypes.c_void_p):
5656
self._on_definitions_changed_refs = None # Keeping references to avoid GC.
5757

5858
def _finalizerfunc(self):
59+
self._on_data_received_dispose()
60+
self._on_raw_received_dispose()
61+
self._on_dataframe_received_dispose()
62+
self._on_definitions_changed_dispose()
63+
64+
def dispose(self) -> None:
5965
[buffer.dispose() for buffer in self._buffers]
6066
self._on_data_received_dispose()
6167
self._on_raw_received_dispose()

src/PythonClient/src/quixstreams/models/streamconsumer/timeseriesbufferconsumer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def __init__(self, stream_consumer, net_pointer: ctypes.c_void_p = None):
2828
raise Exception("TimeseriesBufferConsumer is none")
2929

3030
self._interop = tsbci(net_pointer)
31-
TimeseriesBuffer.__init__(self, stream_consumer, net_pointer)
31+
super().__init__(stream_consumer, net_pointer)
3232

3333
def get_net_pointer(self) -> ctypes.c_void_p:
3434
"""

0 commit comments

Comments
 (0)