Optimized transport data persistence to avoid CPU spikes on low-powered systems

This commit is contained in:
Mark Qvist
2026-07-20 13:55:53 +02:00
parent 2b79db03c0
commit cbba3502b7
+78 -44
View File
@@ -238,16 +238,19 @@ class Transport:
if RNS.Reticulum.local_hops_delta(): Transport.local_hops_delta = (ord(os.urandom(1))%6)+2 if RNS.Reticulum.local_hops_delta(): Transport.local_hops_delta = (ord(os.urandom(1))%6)+2
packet_hashlist_path = RNS.Reticulum.storagepath+"/packet_hashlist" packet_hashlist_path = RNS.Reticulum.storagepath+"/packet_hashlist.raw"
if not Transport.owner.is_connected_to_shared_instance: if RNS.Reticulum.transport_enabled() and not Transport.owner.is_connected_to_shared_instance:
if os.path.isfile(packet_hashlist_path): if os.path.isfile(packet_hashlist_path):
try: try:
file = open(packet_hashlist_path, "rb") with open(packet_hashlist_path, "rb") as file:
hashlist_data = umsgpack.unpackb(file.read()) hashlen = RNS.Identity.HASHLENGTH//8
Transport.packet_hashlist = set(hashlist_data) done = False
file.close() while not done:
except Exception as e: packet_hash = file.read(hashlen)
RNS.log("Could not load packet hashlist from storage, the contained exception was: "+str(e), RNS.LOG_ERROR) if len(packet_hash) == hashlen: Transport.packet_hashlist.add(packet_hash)
else: done = True
except Exception as e: RNS.log("Could not load packet hashlist from storage, the contained exception was: "+str(e), RNS.LOG_ERROR)
Transport.reload_blackhole() Transport.reload_blackhole()
@@ -2513,6 +2516,10 @@ class Transport:
gc.collect() gc.collect()
@staticmethod
def interface_hashes():
return {interface.get_hash() for interface in Transport.interfaces}
@staticmethod @staticmethod
def find_interface_from_hash(interface_hash): def find_interface_from_hash(interface_hash):
for interface in Transport.interfaces: for interface in Transport.interfaces:
@@ -3222,6 +3229,7 @@ class Transport:
@staticmethod @staticmethod
def save_packet_hashlist(background=False): def save_packet_hashlist(background=False):
if not Transport.owner.is_connected_to_shared_instance: if not Transport.owner.is_connected_to_shared_instance:
if not RNS.Reticulum.transport_enabled(): return
if hasattr(Transport, "saving_packet_hashlist"): if hasattr(Transport, "saving_packet_hashlist"):
wait_interval = 0.2 wait_interval = 0.2
wait_timeout = 5 wait_timeout = 5
@@ -3236,23 +3244,28 @@ class Transport:
Transport.saving_packet_hashlist = True Transport.saving_packet_hashlist = True
save_start = time.time() save_start = time.time()
if not RNS.Reticulum.transport_enabled(): Transport.packet_hashlist = set() if RNS.Reticulum.transport_enabled(): RNS.log("Saving packet hashlist to storage...", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
else: RNS.log("Saving packet hashlist to storage...", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None else: return
packet_hashlist_path = RNS.Reticulum.storagepath+"/packet_hashlist" round_started_at = save_start
file = open(packet_hashlist_path, "wb") yield_threshold = 0.010
file.write(umsgpack.packb(list(Transport.packet_hashlist.copy())))
file.close()
save_time = time.time() - save_start packet_hashlist_path = RNS.Reticulum.storagepath+"/packet_hashlist.raw"
if save_time < 1: time_str = str(round(save_time*1000,2))+"ms" with open(packet_hashlist_path, "wb") as file:
else: time_str = str(round(save_time,2))+"s" for packet_hash in Transport.packet_hashlist.copy():
RNS.log("Saved packet hashlist in "+time_str, RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None if background:
if time.time() - round_started_at > yield_threshold:
# Low priority, yield thread
round_started_at = time.time()
time.sleep(0.001)
except Exception as e: file.write(packet_hash)
RNS.log("Could not save packet hashlist to storage, the contained exception was: "+str(e), RNS.LOG_ERROR)
RNS.log(f"Saved packet hashlist in {RNS.prettyshorttime(time.time()-save_start)}", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
except Exception as e: RNS.log("Could not save packet hashlist to storage, the contained exception was: "+str(e), RNS.LOG_ERROR)
finally: Transport.saving_packet_hashlist = False
Transport.saving_packet_hashlist = False
gc.collect() gc.collect()
@@ -3274,40 +3287,52 @@ class Transport:
save_start = time.time() save_start = time.time()
RNS.log("Saving path table to storage...", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None RNS.log("Saving path table to storage...", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
serialised_destinations = [] serialised_destinations = []
path_table = Transport.path_table.copy() path_table = Transport.path_table.copy()
interface_hashes_updated_at = 0
round_started_at = save_start
yield_threshold = 0.010
for destination_hash in path_table: for destination_hash in path_table:
if background:
if time.time() - round_started_at > yield_threshold:
# Low priority, yield thread
round_started_at = time.time()
time.sleep(0.001)
try: try:
# Throttle interface hash lookup to 2 seconds
if time.time() > interface_hashes_updated_at + 2:
interface_hashes = Transport.interface_hashes()
# Get the destination entry from the destination table # Get the destination entry from the destination table
de = path_table[destination_hash] de = path_table[destination_hash]
interface_hash = de[IDX_PT_RVCD_IF].get_hash() interface = de[IDX_PT_RVCD_IF]
# Only store destination table entry if the associated # Only store destination table entry if the associated
# interface is still active # interface is still active
interface = Transport.find_interface_from_hash(interface_hash) if not interface.get_hash() in interface_hashes: RNS.log(f"Skipping persist for path table entry {RNS.prettyhexrep(destination_hash)}, interface {interface} no longer active", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
if interface != None: else:
# Get the destination entry from the destination table # Get the destination entry from the destination table
if not destination_hash in path_table: if not destination_hash in path_table:
RNS.log(f"Skipping persist for path table entry {RNS.prettyhexrep(destination_hash)}, no longer in table", RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None RNS.log(f"Skipping persist for path table entry {RNS.prettyhexrep(destination_hash)}, no longer in table", RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
de = path_table[destination_hash] de = path_table[destination_hash]
timestamp = de[IDX_PT_TIMESTAMP] timestamp = de[IDX_PT_TIMESTAMP]
received_from = de[IDX_PT_NEXT_HOP] received_from = de[IDX_PT_NEXT_HOP]
hops = de[IDX_PT_HOPS] hops = de[IDX_PT_HOPS]
expires = de[IDX_PT_EXPIRES] expires = de[IDX_PT_EXPIRES]
random_blobs = de[IDX_PT_RANDBLOBS] random_blobs = de[IDX_PT_RANDBLOBS]
packet_hash = de[IDX_PT_PACKET] packet_hash = de[IDX_PT_PACKET]
interface_hash = interface.get_hash()
serialised_entry = [ serialised_entry = [ destination_hash,
destination_hash, timestamp,
timestamp, received_from,
received_from, hops,
hops, expires,
expires, random_blobs,
random_blobs, interface_hash,
interface_hash, packet_hash ]
packet_hash
]
serialised_destinations.append(serialised_entry) serialised_destinations.append(serialised_entry)
@@ -3353,7 +3378,16 @@ class Transport:
RNS.log("Saving tunnel table to storage...", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None RNS.log("Saving tunnel table to storage...", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
serialised_tunnels = [] serialised_tunnels = []
round_started_at = save_start
yield_threshold = 0.010
for tunnel_id in Transport.tunnels.copy(): for tunnel_id in Transport.tunnels.copy():
if background:
if time.time() - round_started_at > yield_threshold:
# Low priority, yield thread
round_started_at = time.time()
time.sleep(0.001)
te = Transport.tunnels[tunnel_id] te = Transport.tunnels[tunnel_id]
interface = te[1] interface = te[1]
tunnel_paths = te[2].copy() tunnel_paths = te[2].copy()