Skip to content

Commit

Permalink
gattlib-py: Fix some memory leaks
Browse files Browse the repository at this point in the history
  • Loading branch information
oliviermartin committed Mar 13, 2024
1 parent d2fb01d commit b2c4094
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 8 deletions.
7 changes: 7 additions & 0 deletions common/gattlib_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -191,3 +191,10 @@ void gattlib_handler_dispatch_to_thread(struct gattlib_handler* handler, void (*
return;
}
}

// Helper function to free memory from Python frontend
void gattlib_free_mem(void *ptr) {
if (ptr != NULL) {
free(ptr);
}
}
11 changes: 9 additions & 2 deletions common/gattlib_eddystone.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ struct on_eddystone_discovered_device_arg {
static void on_eddystone_discovered_device(void *adapter, const char* addr, const char* name, void *user_data)
{
struct on_eddystone_discovered_device_arg *callback_data = user_data;
gattlib_advertisement_data_t *advertisement_data;
gattlib_advertisement_data_t *advertisement_data = NULL;
size_t advertisement_data_count;
uint16_t manufacturer_id;
uint8_t *manufacturer_data;
uint8_t *manufacturer_data = NULL;
size_t manufacturer_data_size;
int ret;

Expand All @@ -46,6 +46,13 @@ static void on_eddystone_discovered_device(void *adapter, const char* addr, cons
advertisement_data, advertisement_data_count,
manufacturer_id, manufacturer_data, manufacturer_data_size,
callback_data->user_data);

if (advertisement_data != NULL) {
free(advertisement_data);
}
if (manufacturer_data != NULL) {
free(manufacturer_data);
}
}

int gattlib_adapter_scan_eddystone(void *adapter, int16_t rssi_threshold, uint32_t eddystone_types,
Expand Down
4 changes: 4 additions & 0 deletions gattlib-py/gattlib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,3 +217,7 @@ class GattlibAdvertisementData(Structure):
# int gattlib_mainloop_python(PyObject *handler, PyObject *user_data)
gattlib_mainloop = gattlib.gattlib_mainloop_python
gattlib_mainloop.argtypes = [py_object, py_object]

# void gattlib_free_mem(void *ptr])
gattlib_free_mem = gattlib.gattlib_free_mem
gattlib_free_mem.argtypes = [c_void_p]
3 changes: 3 additions & 0 deletions gattlib-py/gattlib/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,4 +268,7 @@ def gattlib_get_advertisement_data_from_mac(self, mac_address):
for i in range(_manufacturer_data_len.value):
manufacturer_data[i] = c_bytearray.contents[i] & 0xFF

gattlib_free_mem(_advertisement_data)
gattlib_free_mem(_manufacturer_data)

return advertisement_data, _manufacturer_id.value, manufacturer_data
37 changes: 31 additions & 6 deletions gattlib-py/gattlib/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ def __init__(self, adapter: Adapter, addr: str, name: str = None):
self._addr = addr
self._name = name
self._connection = None
# We use a lock because on disconnection, we will set self._connection to None
self._connection_lock = threading.Lock()
# We use a lock on disconnection to ensure the memory is safely freed
self._disconnection_lock = threading.Lock()

self.on_connection_callback = None
self.on_connection_error_callback = None
Expand All @@ -51,6 +54,10 @@ def __init__(self, adapter: Adapter, addr: str, name: str = None):
# Dictionnary for GATT characteristic callback
self._gatt_characteristic_callbacks = {}

# Memory that could be allocated by native gattlib
self._services_ptr = None
self._characteristics_ptr = None

@property
def mac_address(self) -> str:
"""Return Device MAC Address"""
Expand Down Expand Up @@ -109,9 +116,24 @@ def register_on_disconnect(self, callback, user_data=None):
self.disconnection_callback = callback

def on_disconnection(user_data):
self._disconnection_lock.acquire()

if self.disconnection_callback:
self.disconnection_callback()

# On disconnection, we do not need the list of GATT services and GATT characteristics
if self._services_ptr:
gattlib_free_mem(self._services_ptr)
self._services_ptr = None
if self._characteristics_ptr:
gattlib_free_mem(self._characteristics_ptr)
self._characteristics_ptr = None

# Reset the connection handler
self._connection = None

self._disconnection_lock.release()

gattlib_register_on_disconnect(self.connection,
gattlib_disconnected_device_python_callback,
gattlib_python_callback_args(on_disconnection, user_data))
Expand All @@ -130,29 +152,29 @@ def discover(self):
#
# Discover GATT Services
#
_services = POINTER(GattlibPrimaryService)()
self._services_ptr = POINTER(GattlibPrimaryService)()
_services_count = c_int(0)
ret = gattlib_discover_primary(self.connection, byref(_services), byref(_services_count))
ret = gattlib_discover_primary(self.connection, byref(self._services_ptr), byref(_services_count))
handle_return(ret)

self._services = {}
for i in range(0, _services_count.value):
service = GattService(self, _services[i])
service = GattService(self, self._services_ptr[i])
self._services[service.short_uuid] = service

logger.debug("Service UUID:0x%x" % service.short_uuid)

#
# Discover GATT Characteristics
#
_characteristics = POINTER(GattlibCharacteristic)()
self._characteristics_ptr = POINTER(GattlibCharacteristic)()
_characteristics_count = c_int(0)
ret = gattlib_discover_char(self.connection, byref(_characteristics), byref(_characteristics_count))
ret = gattlib_discover_char(self.connection, byref(self._characteristics_ptr), byref(_characteristics_count))
handle_return(ret)

self._characteristics = {}
for i in range(0, _characteristics_count.value):
characteristic = GattCharacteristic(self, _characteristics[i])
characteristic = GattCharacteristic(self, self._characteristics_ptr[i])
self._characteristics[characteristic.short_uuid] = characteristic

logger.debug("Characteristic UUID:0x%x" % characteristic.short_uuid)
Expand Down Expand Up @@ -201,6 +223,9 @@ def get_advertisement_data(self):
for i in range(_manufacturer_data_len.value):
manufacturer_data[i] = c_bytearray.contents[i] & 0xFF

gattlib_free_mem(_advertisement_data)
gattlib_free_mem(_manufacturer_data)

return advertisement_data, _manufacturer_id.value, manufacturer_data

@property
Expand Down

0 comments on commit b2c4094

Please sign in to comment.