$darkmode
Elektra 0.11.0
|
The current public API allows various kind of sequences of kdbGet
and kdbSet
calls. There is no real guideline of allowed and/or prohibited sequences, other than kdbGet
for a certain parent key must be called before kdbSet
:
kdbOpen
kdbSet
We discovered that some sequences are currently legal but problematic for change tracking and maybe also other use cases.
Currently, notification plugins assume that a kdbSet
operation for a certain parent key always directly follows the kdbGet
operation for that same key. However, this is not enforced in any way within Elektra, nor is it documented that it should.
In applications, kdbGet
and kdbSet
of different parent keys might be interwoven in any way. This is problematic, as plugins may store the result of kdbGet
and use the stored data in the following kdbSet
to calculate a changeset. Plugins have to do this, as there isn't any other Elektra-provided mechanism currently to do change tracking for them.
Note, that erroneous behavior only occurs if plugins are used globally, i.e. as notification-send
hook plugins. If they are mounted for specific backends (via kdb mount
), this behavior will not occur as plugins from different backends are isolated.
The following example will demonstrate the problematic sequence as real, runnable code. This example will show the erroneous behavior for the dbus
and logchange
plugins, if used as notification-send
hooks. These both plugins just serve as a general demonstration, and there may be more plugins that do change tracking this way.
First, run some setup steps:
Then, run the following command to monitor the notifications by the dbus
plugins:
Finally, execute the following program:
(A)
the plugins internally store the data below the parent keyA
.(B)
the same thing happens for the data below the parent keyB
. This replaces the data from (A)
.(C)
plugins that do change tracking need to generate a changeset. The changeset will be calculated between the stored data and the new data in the KeySet a
. Because the stored data is now from (B)
and not from (A)
, this changeset will be wrong. The calculation will wrongly assume everything in KeySet b
was removed and everything in KeySet a
is newly added.Right after (B)
, the two keysets are filled with the following values:
a | b |
---|---|
user:/a/brightness | user:/b/background |
user:/a/saturation | user:/b/foreground |
If a plugin calculates a changeset during kdbSet
by comparing to the KeySet
from the last kdbGet
(as described above), the plugin will wrongly calculate the following changeset at (C)
:
Added Keys | Removed Keys | Modified Keys |
---|---|---|
user:/a/brightness | user:/b/background | |
user:/a/saturation | user:/b/foreground | |
user:/a/test |
This is obviously wrong. Looking at the example above, the changeset should only contain user:/a/test
, as nothing else has been changed in keyset a
. So the correct changeset should look like:
Added Keys | Removed Keys | Modified Keys |
---|---|---|
user:/a/test |
The erroneous behavior can be noticed by the output of dbus-monitor
:
The same behavior is present in the logchange
plugin. Notice its output onto stdout
:
As can be seen, the change tracking within the dbus
and logchange
plugins wrongly calculates everything below user:/a
are new keys that have been added, and everything below user:/b
was removed.
1. 2. 3.
Document which sequences are allowed without raising errors in all illegal cases. Least-effort approach, but at least offers some transparency for developers. Developers utilizing Elektra can still ignore it, and may cause problems in certain setups without knowing.
(Violates constraint 1)
parentKey
used in kdbSet
is below the one used in the last kdbGet
. If this is not the case, kdbSet
will abort and report an error in parentKey
. Developers might still wrongly mix the sequences, but they will get an error and have to fix it.Key * lastGetParent
to struct _KDB
keyCopy (handle->lastGetParent, parentKey, KEY_CP_NAME)
in kdbGet
keyIsSameOrBelow (handle->lastGetParent, parentKey) == 1
in kdbSet
ks
used in kdbSet
is the same as in the last kdbGet
Similar to (2), but we check for the pointer of the KeySet
instead of having a copy of the parent key, which saves some memory.KeySet * lastGetKs
to struct _KDB
handle->lastGetKs = ks
in kdbGet
lastGetKs == ks
in kdbSet
KeySet
. This solution completely eliminates problematic sequences.kdbGet
. This will increase memory usage. However, this could be paired with the COW semantics so the memory toll would not be that big of a deal. The biggest problem with this approach would be the unnecessary duplication of the non-trivial change tracking algorithm.Don't restrict sequences further and provide a common framework to handle change tracking correctly.
As the problem has only been observed with plugins doing their own change tracking, we could provide a general change tracking framework within Elektra. This way, we have only one such algorithm in a central place, and plugin authors don't have to think about the sequences their plugins are called by developers.
This approach can also be paired with COW semantics, so that memory toll will be kept low. A separate decision for change tracking is currently in progress.
Should we observe this problem with use cases other than change tracking, we can provide general frameworks for those too.