Params refactor, simplified (#2300)
* always c++ * Create C++ params class * get works * tests hang now * passes tests * cleanup string formatting * handle interrupt in blocking read * fix memory leak * remove unused constructor * Use delete_db_value directly * Rename put -> write_db_value * filename cleanup * no semicolons in cython * Update common/SConscript Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com> * add std::string version of delete_db_value * This is handled * cleanup encoding * Add clear method to clear all * add persistent params * fix android build * Should be called clear_all * only import params when needed * set params path on manager import * recusrively create directories * Fix function order * cleanup mkdirp Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com> Co-authored-by: Comma Device <device@comma.ai>pull/2324/head
parent
a4ccfcffe8
commit
2e182e5c57
20 changed files with 417 additions and 577 deletions
@ -1,409 +1,4 @@ |
||||
#!/usr/bin/env python3 |
||||
"""ROS has a parameter server, we have files. |
||||
|
||||
The parameter store is a persistent key value store, implemented as a directory with a writer lock. |
||||
On Android, we store params under params_dir = /data/params. The writer lock is a file |
||||
"<params_dir>/.lock" taken using flock(), and data is stored in a directory symlinked to by |
||||
"<params_dir>/d". |
||||
|
||||
Each key, value pair is stored as a file with named <key> with contents <value>, located in |
||||
<params_dir>/d/<key> |
||||
|
||||
Readers of a single key can just open("<params_dir>/d/<key>") and read the file contents. |
||||
Readers who want a consistent snapshot of multiple keys should take the lock. |
||||
|
||||
Writers should take the lock before modifying anything. Writers should also leave the DB in a |
||||
consistent state after a crash. The implementation below does this by copying all params to a temp |
||||
directory <params_dir>/<tmp>, then atomically symlinking <params_dir>/<d> to <params_dir>/<tmp> |
||||
before deleting the old <params_dir>/<d> directory. |
||||
|
||||
Writers that only modify a single key can simply take the lock, then swap the corresponding value |
||||
file in place without messing with <params_dir>/d. |
||||
""" |
||||
import time |
||||
import os |
||||
import errno |
||||
import shutil |
||||
import fcntl |
||||
import tempfile |
||||
import threading |
||||
from enum import Enum |
||||
from common.basedir import PARAMS |
||||
|
||||
|
||||
def mkdirs_exists_ok(path): |
||||
try: |
||||
os.makedirs(path) |
||||
except OSError: |
||||
if not os.path.isdir(path): |
||||
raise |
||||
|
||||
|
||||
class TxType(Enum): |
||||
PERSISTENT = 1 |
||||
CLEAR_ON_MANAGER_START = 2 |
||||
CLEAR_ON_PANDA_DISCONNECT = 3 |
||||
|
||||
|
||||
class UnknownKeyName(Exception): |
||||
pass |
||||
|
||||
|
||||
keys = { |
||||
"AccessToken": [TxType.CLEAR_ON_MANAGER_START], |
||||
"AthenadPid": [TxType.PERSISTENT], |
||||
"CalibrationParams": [TxType.PERSISTENT], |
||||
"CarBatteryCapacity": [TxType.PERSISTENT], |
||||
"CarParams": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT], |
||||
"CarParamsCache": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT], |
||||
"CarVin": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT], |
||||
"CommunityFeaturesToggle": [TxType.PERSISTENT], |
||||
"CompletedTrainingVersion": [TxType.PERSISTENT], |
||||
"DisablePowerDown": [TxType.PERSISTENT], |
||||
"DisableUpdates": [TxType.PERSISTENT], |
||||
"DoUninstall": [TxType.CLEAR_ON_MANAGER_START], |
||||
"DongleId": [TxType.PERSISTENT], |
||||
"GitBranch": [TxType.PERSISTENT], |
||||
"GitCommit": [TxType.PERSISTENT], |
||||
"GitRemote": [TxType.PERSISTENT], |
||||
"GithubSshKeys": [TxType.PERSISTENT], |
||||
"HasAcceptedTerms": [TxType.PERSISTENT], |
||||
"HasCompletedSetup": [TxType.PERSISTENT], |
||||
"IsDriverViewEnabled": [TxType.CLEAR_ON_MANAGER_START], |
||||
"IsLdwEnabled": [TxType.PERSISTENT], |
||||
"IsMetric": [TxType.PERSISTENT], |
||||
"IsOffroad": [TxType.CLEAR_ON_MANAGER_START], |
||||
"IsRHD": [TxType.PERSISTENT], |
||||
"IsTakingSnapshot": [TxType.CLEAR_ON_MANAGER_START], |
||||
"IsUpdateAvailable": [TxType.CLEAR_ON_MANAGER_START], |
||||
"IsUploadRawEnabled": [TxType.PERSISTENT], |
||||
"LastAthenaPingTime": [TxType.PERSISTENT], |
||||
"LastUpdateTime": [TxType.PERSISTENT], |
||||
"LastUpdateException": [TxType.PERSISTENT], |
||||
"LiveParameters": [TxType.PERSISTENT], |
||||
"OpenpilotEnabledToggle": [TxType.PERSISTENT], |
||||
"LaneChangeEnabled": [TxType.PERSISTENT], |
||||
"PandaFirmware": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT], |
||||
"PandaFirmwareHex": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT], |
||||
"PandaDongleId": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT], |
||||
"Passive": [TxType.PERSISTENT], |
||||
"RecordFront": [TxType.PERSISTENT], |
||||
"ReleaseNotes": [TxType.PERSISTENT], |
||||
"ShouldDoUpdate": [TxType.CLEAR_ON_MANAGER_START], |
||||
"SubscriberInfo": [TxType.PERSISTENT], |
||||
"TermsVersion": [TxType.PERSISTENT], |
||||
"TrainingVersion": [TxType.PERSISTENT], |
||||
"UpdateAvailable": [TxType.CLEAR_ON_MANAGER_START], |
||||
"UpdateFailedCount": [TxType.CLEAR_ON_MANAGER_START], |
||||
"Version": [TxType.PERSISTENT], |
||||
"Offroad_ChargeDisabled": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT], |
||||
"Offroad_ConnectivityNeeded": [TxType.CLEAR_ON_MANAGER_START], |
||||
"Offroad_ConnectivityNeededPrompt": [TxType.CLEAR_ON_MANAGER_START], |
||||
"Offroad_TemperatureTooHigh": [TxType.CLEAR_ON_MANAGER_START], |
||||
"Offroad_PandaFirmwareMismatch": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT], |
||||
"Offroad_InvalidTime": [TxType.CLEAR_ON_MANAGER_START], |
||||
"Offroad_IsTakingSnapshot": [TxType.CLEAR_ON_MANAGER_START], |
||||
"Offroad_NeosUpdate": [TxType.CLEAR_ON_MANAGER_START], |
||||
"Offroad_UpdateFailed": [TxType.CLEAR_ON_MANAGER_START], |
||||
} |
||||
|
||||
|
||||
def fsync_dir(path): |
||||
fd = os.open(path, os.O_RDONLY) |
||||
try: |
||||
os.fsync(fd) |
||||
finally: |
||||
os.close(fd) |
||||
|
||||
|
||||
class FileLock(): |
||||
def __init__(self, path, create, lock_ex): |
||||
self._path = path |
||||
self._create = create |
||||
self._fd = None |
||||
self._lock_ex = lock_ex |
||||
|
||||
def acquire(self): |
||||
self._fd = os.open(self._path, os.O_CREAT if self._create else 0) |
||||
fcntl.flock(self._fd, fcntl.LOCK_EX if self._lock_ex else fcntl.LOCK_SH) |
||||
|
||||
def release(self): |
||||
if self._fd is not None: |
||||
os.close(self._fd) |
||||
self._fd = None |
||||
|
||||
|
||||
class DBAccessor(): |
||||
def __init__(self, path): |
||||
self._path = path |
||||
self._vals = None |
||||
|
||||
def keys(self): |
||||
self._check_entered() |
||||
return self._vals.keys() |
||||
|
||||
def get(self, key): |
||||
self._check_entered() |
||||
|
||||
if self._vals is None: |
||||
return None |
||||
|
||||
try: |
||||
return self._vals[key] |
||||
except KeyError: |
||||
return None |
||||
|
||||
def _get_lock(self, create, lock_ex): |
||||
lock = FileLock(os.path.join(self._path, ".lock"), create, lock_ex) |
||||
lock.acquire() |
||||
return lock |
||||
|
||||
def _read_values_locked(self): |
||||
"""Callers should hold a lock while calling this method.""" |
||||
vals = {} |
||||
try: |
||||
data_path = self._data_path() |
||||
keys = os.listdir(data_path) |
||||
for key in keys: |
||||
with open(os.path.join(data_path, key), "rb") as f: |
||||
vals[key] = f.read() |
||||
except (OSError, IOError) as e: |
||||
# Either the DB hasn't been created yet, or somebody wrote a bug and left the DB in an |
||||
# inconsistent state. Either way, return empty. |
||||
if e.errno == errno.ENOENT: |
||||
return {} |
||||
|
||||
return vals |
||||
|
||||
def _data_path(self): |
||||
return os.path.join(self._path, "d") |
||||
|
||||
def _check_entered(self): |
||||
if self._vals is None: |
||||
raise Exception("Must call __enter__ before using DB") |
||||
|
||||
|
||||
class DBReader(DBAccessor): |
||||
def __enter__(self): |
||||
try: |
||||
lock = self._get_lock(False, False) |
||||
except OSError as e: |
||||
# Do not create lock if it does not exist. |
||||
if e.errno == errno.ENOENT: |
||||
self._vals = {} |
||||
return self |
||||
|
||||
try: |
||||
# Read everything. |
||||
self._vals = self._read_values_locked() |
||||
return self |
||||
finally: |
||||
lock.release() |
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback): |
||||
pass |
||||
|
||||
|
||||
class DBWriter(DBAccessor): |
||||
def __init__(self, path): |
||||
super(DBWriter, self).__init__(path) |
||||
self._lock = None |
||||
self._prev_umask = None |
||||
|
||||
def put(self, key, value): |
||||
self._vals[key] = value |
||||
|
||||
def delete(self, key): |
||||
self._vals.pop(key, None) |
||||
|
||||
def __enter__(self): |
||||
mkdirs_exists_ok(self._path) |
||||
|
||||
# Make sure we can write and that permissions are correct. |
||||
self._prev_umask = os.umask(0) |
||||
|
||||
try: |
||||
os.chmod(self._path, 0o777) |
||||
self._lock = self._get_lock(True, True) |
||||
self._vals = self._read_values_locked() |
||||
except Exception: |
||||
os.umask(self._prev_umask) |
||||
self._prev_umask = None |
||||
raise |
||||
|
||||
return self |
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback): |
||||
self._check_entered() |
||||
|
||||
try: |
||||
# data_path refers to the externally used path to the params. It is a symlink. |
||||
# old_data_path is the path currently pointed to by data_path. |
||||
# tempdir_path is a path where the new params will go, which the new data path will point to. |
||||
# new_data_path is a temporary symlink that will atomically overwrite data_path. |
||||
# |
||||
# The current situation is: |
||||
# data_path -> old_data_path |
||||
# We're going to write params data to tempdir_path |
||||
# tempdir_path -> params data |
||||
# Then point new_data_path to tempdir_path |
||||
# new_data_path -> tempdir_path |
||||
# Then atomically overwrite data_path with new_data_path |
||||
# data_path -> tempdir_path |
||||
old_data_path = None |
||||
new_data_path = None |
||||
tempdir_path = tempfile.mkdtemp(prefix=".tmp", dir=self._path) |
||||
|
||||
try: |
||||
# Write back all keys. |
||||
os.chmod(tempdir_path, 0o777) |
||||
for k, v in self._vals.items(): |
||||
with open(os.path.join(tempdir_path, k), "wb") as f: |
||||
f.write(v) |
||||
f.flush() |
||||
os.fsync(f.fileno()) |
||||
fsync_dir(tempdir_path) |
||||
|
||||
data_path = self._data_path() |
||||
try: |
||||
old_data_path = os.path.join(self._path, os.readlink(data_path)) |
||||
except (OSError, IOError): |
||||
# NOTE(mgraczyk): If other DB implementations have bugs, this could cause |
||||
# copies to be left behind, but we still want to overwrite. |
||||
pass |
||||
|
||||
new_data_path = "{}.link".format(tempdir_path) |
||||
os.symlink(os.path.basename(tempdir_path), new_data_path) |
||||
os.rename(new_data_path, data_path) |
||||
fsync_dir(self._path) |
||||
finally: |
||||
# If the rename worked, we can delete the old data. Otherwise delete the new one. |
||||
success = new_data_path is not None and os.path.exists(data_path) and ( |
||||
os.readlink(data_path) == os.path.basename(tempdir_path)) |
||||
|
||||
if success: |
||||
if old_data_path is not None: |
||||
shutil.rmtree(old_data_path) |
||||
else: |
||||
shutil.rmtree(tempdir_path) |
||||
|
||||
# Regardless of what happened above, there should be no link at new_data_path. |
||||
if new_data_path is not None and os.path.islink(new_data_path): |
||||
os.remove(new_data_path) |
||||
finally: |
||||
os.umask(self._prev_umask) |
||||
self._prev_umask = None |
||||
|
||||
# Always release the lock. |
||||
self._lock.release() |
||||
self._lock = None |
||||
|
||||
|
||||
def read_db(params_path, key): |
||||
path = "%s/d/%s" % (params_path, key) |
||||
try: |
||||
with open(path, "rb") as f: |
||||
return f.read() |
||||
except IOError: |
||||
return None |
||||
|
||||
|
||||
def write_db(params_path, key, value): |
||||
if isinstance(value, str): |
||||
value = value.encode('utf8') |
||||
|
||||
prev_umask = os.umask(0) |
||||
lock = FileLock(params_path + "/.lock", True, True) |
||||
lock.acquire() |
||||
|
||||
try: |
||||
tmp_path = tempfile.NamedTemporaryFile(mode="wb", prefix=".tmp", dir=params_path, delete=False) |
||||
with tmp_path as f: |
||||
f.write(value) |
||||
f.flush() |
||||
os.fsync(f.fileno()) |
||||
os.chmod(tmp_path.name, 0o666) |
||||
|
||||
path = "%s/d/%s" % (params_path, key) |
||||
os.rename(tmp_path.name, path) |
||||
fsync_dir(os.path.dirname(path)) |
||||
finally: |
||||
os.umask(prev_umask) |
||||
lock.release() |
||||
|
||||
|
||||
class Params(): |
||||
def __init__(self, db=PARAMS): |
||||
self.db = db |
||||
|
||||
# create the database if it doesn't exist... |
||||
if not os.path.exists(self.db + "/d"): |
||||
with self.transaction(write=True): |
||||
pass |
||||
|
||||
def clear_all(self): |
||||
shutil.rmtree(self.db, ignore_errors=True) |
||||
with self.transaction(write=True): |
||||
pass |
||||
|
||||
def transaction(self, write=False): |
||||
if write: |
||||
return DBWriter(self.db) |
||||
else: |
||||
return DBReader(self.db) |
||||
|
||||
def _clear_keys_with_type(self, tx_type): |
||||
with self.transaction(write=True) as txn: |
||||
for key in keys: |
||||
if tx_type in keys[key]: |
||||
txn.delete(key) |
||||
|
||||
def manager_start(self): |
||||
self._clear_keys_with_type(TxType.CLEAR_ON_MANAGER_START) |
||||
|
||||
def panda_disconnect(self): |
||||
self._clear_keys_with_type(TxType.CLEAR_ON_PANDA_DISCONNECT) |
||||
|
||||
def delete(self, key): |
||||
with self.transaction(write=True) as txn: |
||||
txn.delete(key) |
||||
|
||||
def get(self, key, block=False, encoding=None): |
||||
if key not in keys: |
||||
raise UnknownKeyName(key) |
||||
|
||||
while 1: |
||||
ret = read_db(self.db, key) |
||||
if not block or ret is not None: |
||||
break |
||||
# is polling really the best we can do? |
||||
time.sleep(0.05) |
||||
|
||||
if ret is not None and encoding is not None: |
||||
ret = ret.decode(encoding) |
||||
|
||||
return ret |
||||
|
||||
def put(self, key, dat): |
||||
""" |
||||
Warning: This function blocks until the param is written to disk! |
||||
In very rare cases this can take over a second, and your code will hang. |
||||
|
||||
Use the put_nonblocking helper function in time sensitive code, but |
||||
in general try to avoid writing params as much as possible. |
||||
""" |
||||
|
||||
if key not in keys: |
||||
raise UnknownKeyName(key) |
||||
|
||||
write_db(self.db, key, dat) |
||||
|
||||
|
||||
def put_nonblocking(key, val): |
||||
def f(key, val): |
||||
params = Params() |
||||
params.put(key, val) |
||||
|
||||
t = threading.Thread(target=f, args=(key, val)) |
||||
t.start() |
||||
return t |
||||
from common.params_pyx import Params, UnknownKeyName, put_nonblocking # pylint: disable=no-name-in-module, import-error |
||||
assert Params |
||||
assert UnknownKeyName |
||||
assert put_nonblocking |
||||
|
@ -0,0 +1,16 @@ |
||||
from libcpp.string cimport string |
||||
from libcpp cimport bool |
||||
|
||||
cdef extern from "selfdrive/common/params.cc": |
||||
pass |
||||
|
||||
cdef extern from "selfdrive/common/util.c": |
||||
pass |
||||
|
||||
cdef extern from "selfdrive/common/params.h": |
||||
cdef cppclass Params: |
||||
Params(bool) |
||||
Params(string) |
||||
string get(string, bool) nogil |
||||
int delete_db_value(string) |
||||
int write_db_value(string, string) |
@ -0,0 +1,157 @@ |
||||
# distutils: langauge = c++ |
||||
import threading |
||||
import os |
||||
from libcpp cimport bool |
||||
from libcpp.string cimport string |
||||
from params_pxd cimport Params as c_Params |
||||
|
||||
cdef enum TxType: |
||||
PERSISTENT = 1 |
||||
CLEAR_ON_MANAGER_START = 2 |
||||
CLEAR_ON_PANDA_DISCONNECT = 3 |
||||
|
||||
keys = { |
||||
b"AccessToken": [TxType.CLEAR_ON_MANAGER_START], |
||||
b"AthenadPid": [TxType.PERSISTENT], |
||||
b"CalibrationParams": [TxType.PERSISTENT], |
||||
b"CarBatteryCapacity": [TxType.PERSISTENT], |
||||
b"CarParams": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT], |
||||
b"CarParamsCache": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT], |
||||
b"CarVin": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT], |
||||
b"CommunityFeaturesToggle": [TxType.PERSISTENT], |
||||
b"CompletedTrainingVersion": [TxType.PERSISTENT], |
||||
b"DisablePowerDown": [TxType.PERSISTENT], |
||||
b"DisableUpdates": [TxType.PERSISTENT], |
||||
b"DoUninstall": [TxType.CLEAR_ON_MANAGER_START], |
||||
b"DongleId": [TxType.PERSISTENT], |
||||
b"GitBranch": [TxType.PERSISTENT], |
||||
b"GitCommit": [TxType.PERSISTENT], |
||||
b"GitRemote": [TxType.PERSISTENT], |
||||
b"GithubSshKeys": [TxType.PERSISTENT], |
||||
b"HasAcceptedTerms": [TxType.PERSISTENT], |
||||
b"HasCompletedSetup": [TxType.PERSISTENT], |
||||
b"IsDriverViewEnabled": [TxType.CLEAR_ON_MANAGER_START], |
||||
b"IsLdwEnabled": [TxType.PERSISTENT], |
||||
b"IsMetric": [TxType.PERSISTENT], |
||||
b"IsOffroad": [TxType.CLEAR_ON_MANAGER_START], |
||||
b"IsRHD": [TxType.PERSISTENT], |
||||
b"IsTakingSnapshot": [TxType.CLEAR_ON_MANAGER_START], |
||||
b"IsUpdateAvailable": [TxType.CLEAR_ON_MANAGER_START], |
||||
b"IsUploadRawEnabled": [TxType.PERSISTENT], |
||||
b"LastAthenaPingTime": [TxType.PERSISTENT], |
||||
b"LastUpdateTime": [TxType.PERSISTENT], |
||||
b"LastUpdateException": [TxType.PERSISTENT], |
||||
b"LiveParameters": [TxType.PERSISTENT], |
||||
b"OpenpilotEnabledToggle": [TxType.PERSISTENT], |
||||
b"LaneChangeEnabled": [TxType.PERSISTENT], |
||||
b"PandaFirmware": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT], |
||||
b"PandaFirmwareHex": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT], |
||||
b"PandaDongleId": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT], |
||||
b"Passive": [TxType.PERSISTENT], |
||||
b"RecordFront": [TxType.PERSISTENT], |
||||
b"ReleaseNotes": [TxType.PERSISTENT], |
||||
b"ShouldDoUpdate": [TxType.CLEAR_ON_MANAGER_START], |
||||
b"SubscriberInfo": [TxType.PERSISTENT], |
||||
b"TermsVersion": [TxType.PERSISTENT], |
||||
b"TrainingVersion": [TxType.PERSISTENT], |
||||
b"UpdateAvailable": [TxType.CLEAR_ON_MANAGER_START], |
||||
b"UpdateFailedCount": [TxType.CLEAR_ON_MANAGER_START], |
||||
b"Version": [TxType.PERSISTENT], |
||||
b"Offroad_ChargeDisabled": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT], |
||||
b"Offroad_ConnectivityNeeded": [TxType.CLEAR_ON_MANAGER_START], |
||||
b"Offroad_ConnectivityNeededPrompt": [TxType.CLEAR_ON_MANAGER_START], |
||||
b"Offroad_TemperatureTooHigh": [TxType.CLEAR_ON_MANAGER_START], |
||||
b"Offroad_PandaFirmwareMismatch": [TxType.CLEAR_ON_MANAGER_START, TxType.CLEAR_ON_PANDA_DISCONNECT], |
||||
b"Offroad_InvalidTime": [TxType.CLEAR_ON_MANAGER_START], |
||||
b"Offroad_IsTakingSnapshot": [TxType.CLEAR_ON_MANAGER_START], |
||||
b"Offroad_NeosUpdate": [TxType.CLEAR_ON_MANAGER_START], |
||||
b"Offroad_UpdateFailed": [TxType.CLEAR_ON_MANAGER_START], |
||||
} |
||||
|
||||
def ensure_bytes(v): |
||||
if isinstance(v, str): |
||||
return v.encode() |
||||
else: |
||||
return v |
||||
|
||||
|
||||
class UnknownKeyName(Exception): |
||||
pass |
||||
|
||||
cdef class Params: |
||||
cdef c_Params* p |
||||
|
||||
def __cinit__(self, d=None, persistent_params=False): |
||||
if d is not None: |
||||
self.p = new c_Params(<string>d.encode()) |
||||
else: |
||||
self.p = new c_Params(<bool>persistent_params) |
||||
|
||||
def __dealloc__(self): |
||||
del self.p |
||||
|
||||
def clear_all(self, tx_type=None): |
||||
for key in keys: |
||||
if tx_type is None or tx_type in keys[key]: |
||||
self.delete(key) |
||||
|
||||
def manager_start(self): |
||||
self.clear_all(TxType.CLEAR_ON_MANAGER_START) |
||||
|
||||
def panda_disconnect(self): |
||||
self.clear_all(TxType.CLEAR_ON_PANDA_DISCONNECT) |
||||
|
||||
def get(self, key, block=False, encoding=None): |
||||
key = ensure_bytes(key) |
||||
|
||||
if key not in keys: |
||||
raise UnknownKeyName(key) |
||||
|
||||
cdef string k = key |
||||
cdef bool b = block |
||||
|
||||
cdef string val |
||||
with nogil: |
||||
val = self.p.get(k, b) |
||||
|
||||
if val == b"": |
||||
if block: |
||||
# If we got no value while running in blocked mode |
||||
# it means we got an interrupt while waiting |
||||
raise KeyboardInterrupt |
||||
else: |
||||
return None |
||||
|
||||
if encoding is not None: |
||||
return val.decode(encoding) |
||||
else: |
||||
return val |
||||
|
||||
def put(self, key, dat): |
||||
""" |
||||
Warning: This function blocks until the param is written to disk! |
||||
In very rare cases this can take over a second, and your code will hang. |
||||
Use the put_nonblocking helper function in time sensitive code, but |
||||
in general try to avoid writing params as much as possible. |
||||
""" |
||||
key = ensure_bytes(key) |
||||
dat = ensure_bytes(dat) |
||||
|
||||
if key not in keys: |
||||
raise UnknownKeyName(key) |
||||
|
||||
self.p.write_db_value(key, dat) |
||||
|
||||
def delete(self, key): |
||||
key = ensure_bytes(key) |
||||
self.p.delete_db_value(key) |
||||
|
||||
|
||||
def put_nonblocking(key, val, d=None): |
||||
def f(key, val): |
||||
params = Params(d) |
||||
params.put(key, val) |
||||
|
||||
t = threading.Thread(target=f, args=(key, val)) |
||||
t.start() |
||||
return t |
@ -0,0 +1,21 @@ |
||||
import os |
||||
from distutils.core import Extension, setup |
||||
from Cython.Build import cythonize |
||||
from common.cython_hacks import BuildExtWithoutPlatformSuffix |
||||
from common.basedir import BASEDIR |
||||
|
||||
sourcefiles = ['params_pyx.pyx'] |
||||
extra_compile_args = ["-std=c++11"] |
||||
|
||||
setup(name='common', |
||||
cmdclass={'build_ext': BuildExtWithoutPlatformSuffix}, |
||||
ext_modules=cythonize( |
||||
Extension( |
||||
"params_pyx", |
||||
language="c++", |
||||
sources=sourcefiles, |
||||
include_dirs=[BASEDIR, os.path.join(BASEDIR, 'selfdrive')], |
||||
extra_compile_args=extra_compile_args |
||||
) |
||||
) |
||||
) |
@ -1,44 +1,47 @@ |
||||
#pragma once |
||||
#include <stddef.h> |
||||
|
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
|
||||
#define ERR_NO_VALUE -33 |
||||
|
||||
int write_db_value(const char* key, const char* value, size_t value_size, bool persistent_param = false); |
||||
|
||||
// Reads a value from the params database.
|
||||
// Inputs:
|
||||
// key: The key to read.
|
||||
// value: A pointer where a newly allocated string containing the db value will
|
||||
// be written.
|
||||
// value_sz: A pointer where the size of value will be written. Does not
|
||||
// include the NULL terminator.
|
||||
// persistent_param: Boolean indicating if the param store in the /persist partition is to be used.
|
||||
// e.g. for sensor calibration files. Will not be cleared after wipe or re-install.
|
||||
//
|
||||
// Returns: Negative on failure, otherwise 0.
|
||||
int read_db_value(const char* key, char** value, size_t* value_sz, bool persistent_param = false); |
||||
|
||||
// Delete a value from the params database.
|
||||
// Inputs are the same as read_db_value, without value and value_sz.
|
||||
int delete_db_value(const char* key, bool persistent_param = false); |
||||
|
||||
// Reads a value from the params database, blocking until successful.
|
||||
// Inputs are the same as read_db_value.
|
||||
void read_db_value_blocking(const char* key, char** value, size_t* value_sz, bool persistent_param = false); |
||||
|
||||
#ifdef __cplusplus |
||||
} // extern "C"
|
||||
#endif |
||||
|
||||
#ifdef __cplusplus |
||||
#include <map> |
||||
#include <string> |
||||
#include <vector> |
||||
int read_db_all(std::map<std::string, std::string> *params, bool persistent_param = false); |
||||
std::vector<char> read_db_bytes(const char* param_name, bool persistent_param = false); |
||||
bool read_db_bool(const char* param_name, bool persistent_param = false); |
||||
#endif |
||||
|
||||
#define ERR_NO_VALUE -33 |
||||
|
||||
class Params { |
||||
private: |
||||
std::string params_path; |
||||
|
||||
public: |
||||
Params(bool persistent_param = false); |
||||
Params(std::string path); |
||||
|
||||
int write_db_value(std::string key, std::string dat); |
||||
int write_db_value(const char* key, const char* value, size_t value_size); |
||||
|
||||
// Reads a value from the params database.
|
||||
// Inputs:
|
||||
// key: The key to read.
|
||||
// value: A pointer where a newly allocated string containing the db value will
|
||||
// be written.
|
||||
// value_sz: A pointer where the size of value will be written. Does not
|
||||
// include the NULL terminator.
|
||||
// persistent_param: Boolean indicating if the param store in the /persist partition is to be used.
|
||||
// e.g. for sensor calibration files. Will not be cleared after wipe or re-install.
|
||||
//
|
||||
// Returns: Negative on failure, otherwise 0.
|
||||
int read_db_value(const char* key, char** value, size_t* value_sz); |
||||
|
||||
// Delete a value from the params database.
|
||||
// Inputs are the same as read_db_value, without value and value_sz.
|
||||
int delete_db_value(std::string key); |
||||
int delete_db_value(const char* key); |
||||
|
||||
// Reads a value from the params database, blocking until successful.
|
||||
// Inputs are the same as read_db_value.
|
||||
int read_db_value_blocking(const char* key, char** value, size_t* value_sz); |
||||
|
||||
int read_db_all(std::map<std::string, std::string> *params); |
||||
std::vector<char> read_db_bytes(const char* param_name); |
||||
bool read_db_bool(const char* param_name); |
||||
|
||||
std::string get(std::string key, bool block=false); |
||||
}; |
||||
|
Loading…
Reference in new issue