-
Notifications
You must be signed in to change notification settings - Fork 123
doc: add decision docs for decision meeting 15.07.2021 #3919
Changes from all commits
8cbdf5c
e12835f
b00ace8
1ba13d5
534ae44
51ae3a2
d248c88
d0b9fb7
a88afe3
e933563
377730d
fa75e44
f434c37
775cdcd
90e8b10
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# Error Handling | ||
|
||
## Problem | ||
|
||
Currently, a lot of functions return the error value, regardless of error. These poses some problems, for instance authors of bindings, where it is hard to tell which error exactly occurred. A big improvement to this would be returning different error codes for different types of errors, in order to be able to provide better information in cases of errors. Some functions also make use of an `errorKey`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Be more concrete. Afaik the only problems are:
|
||
|
||
While this seems like a decent approach, to provide additional informations in cases of error, it is currently very badly documented. It is not really transparent for the users of the function, what kind of information is included in the `errorKey` and even which messages could possibly be returned. | ||
|
||
## Constraints | ||
|
||
We also have to consider that many parts of the API very constrained in what values they can use for error codes. If a function returns a pointer the only possible error value by definition is NULL. | ||
|
||
## Assumptions | ||
|
||
## Considered Alternatives | ||
|
||
- Change return types for many functions across the API | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could also fix the concrete instances of where multiple errors exist. |
||
|
||
## Decision | ||
|
||
- Update documentation in `doc/dev/error-*` and link to them in the documentation | ||
for the module `kdb` | ||
- Add second channel for getting information about errors | ||
- Return error codes directly from functions where failures are expected, e.g. `kdbGet`, `keySetName` | ||
- Harmonize return values from all functions and move error reporting to second channel | ||
|
||
## Rationale | ||
|
||
This simplifies and unifies error reporting across all functions. It also introduces | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On the contrary: it would make it more complicated and if we are not very careful less unified. |
||
a default way for the developers to handle error reporting. Developers of | ||
bindings for Elektra can rely on the existing error reporting feature without | ||
having to introduce custom error messages, that can vary wildly between different | ||
bindings. | ||
|
||
## Implications | ||
|
||
## Related Decisions | ||
|
||
## Notes |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
# Functions copying into buffers | ||
|
||
## Problem | ||
|
||
Currently the way functions like keyGetName() work is by passing a buffer with | ||
a maxSize and if the buffer is large enough, the value gets copied into the | ||
buffer. This leads to the user having to write a lot of surrounding boilerplate | ||
code, checking for the size of every value / name they want to copy into a buffer. | ||
|
||
## Constraints | ||
|
||
## Assumptions | ||
|
||
## Considered Alternatives | ||
|
||
## Decision | ||
|
||
- Remove Functions: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
- keyGetName() | ||
- keyGetUnescapedName() | ||
- keyGetBaseName() | ||
- keyGetString() | ||
- keyGetBinary() | ||
- add documentation in API docu about life-time and add in release notes that you should use strncpy() / memcpy() instead: | ||
|
||
```c | ||
// str values | ||
strncpy(..., keyName (k), ...) | ||
// binary values | ||
memcpy(..., keyValue (k), ...) | ||
``` | ||
|
||
## Rationale | ||
|
||
The functions clutter the API and try to replace existing builtin functionality | ||
for little to no gain. This makes the API leaner while also retaining its | ||
functionality. | ||
|
||
## Implications | ||
|
||
## Related Decisions | ||
|
||
## Notes |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
# Iterators | ||
|
||
## Problem | ||
|
||
The internal iterator inside KeySets seems to cause more problems than it solves. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe write a bit about the background why it was introduced and that this is not used anymore (like discussed in the meeting: to remember errorKey in kdbSet). |
||
|
||
## Constraints | ||
|
||
We could embed one such iterator into KeySets, but recommend that people use an | ||
external instance (int) in new code. | ||
|
||
## Assumptions | ||
|
||
@raphi011 made benchmarks showing that external and internal iterators have | ||
about the same speed. So it should be a safe choice to not provide the internal | ||
iterators for 1.0 and only the external instead. | ||
|
||
## Considered Alternatives | ||
|
||
## Decision | ||
|
||
- remove all functions related to the internal iterator: | ||
- ksRewind | ||
lawli3t marked this conversation as resolved.
Show resolved
Hide resolved
|
||
- ksNext | ||
- ksCurrent | ||
- ksGetCursor | ||
- ksSetCursor | ||
- ksHead | ||
- ksTail | ||
- keyRewindMeta | ||
- keyNextMeta | ||
- keyCurrentMeta | ||
- change `ksAtCursor` to `ksAt` | ||
- add implementation / documentation / tests for the external iterator | ||
- start using external iterators in new code | ||
- remove docu about internal cursor from all functions. | ||
|
||
## Rationale | ||
|
||
- The only function that returns a `cursor_t` is `ksGetCursor`. | ||
Its documentation is completely broken: | ||
- `ksRewind` and `keyNextMeta` cannot be used on the same variable (`ks`). | ||
- The documentation (not just for the above function, but all over the `ks*` | ||
functions) also contains lots of warnings (use only cursor from same keyset, | ||
may be invalid, may become invalid, etc.) that make it seem like there is | ||
something special about these cursors, when in fact they are simply indicies. | ||
There is no information (at least that I could find), how cursors can be | ||
modified. Most of the time it even seems like modifying cursors would be a bad | ||
idea. | ||
|
||
## Implications | ||
|
||
## Related Decisions | ||
|
||
## Notes |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# keyString() return value | ||
|
||
## Problem | ||
|
||
When using keyString() on empty / binary values the return values are the literal strings (null) / (binary). This seems very awkward and unintuitive from a user's perspective. | ||
|
||
## Constraints | ||
|
||
Because the return value of the function is const char\*, there are not so many possibilities for a change to this behavior, as every possible return value - except for a NULL pointer - could just be the value of the string of the Key. Possible changes to this behavior would be: | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sentence stops in the middle? |
||
## Assumptions | ||
|
||
On one hand always returning a string allows things like `strlen(keyString(k)) == 0` to check for an empty string. But at the same time people might expect that `strlen(keyString(k)) < MAX_LEN` also works, but this might silently break code down the line, if `k` is binary and `strlen("(binary)") < MAX_LEN`. | ||
|
||
## Considered Alternatives | ||
|
||
An alternative would be to always store a zero byte after all key values, even if they are binary (might be done already). Then we can safely return `k->data.c == NULL ? "" : k->data.c`. It may contain incomplete data and the `MAX_LEN` problem from above still applies, but there are no segfaults and you don't get return values that have nothing to do with the actual data. | ||
|
||
## Decision | ||
|
||
- `key == NULL` return 0, error code via second channel | ||
- `key->value == NULL` return 0, error code via second channel | ||
- `key == <binary>` return 0, error code via second channel | ||
- everything else as is | ||
|
||
## Rationale | ||
|
||
- 0 seems like the most intuitive value to return in case of an error, although | ||
this introduces the possibility of segfaults for users of the library. printf | ||
doesn't cause segfaults anymore, which improves this problem a lot. | ||
- With the introduction of a second channel for reporting errors, users can check the | ||
error messages in case of segfaults - which alleviates this issue. The first | ||
thing in case of an error should be checking the error message. | ||
|
||
## Implications | ||
|
||
## Related Decisions | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. error_handling.md should be here. |
||
## Notes |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# Reference Counting | ||
|
||
## Problem | ||
|
||
- locking is not reset when ref counting again gets 0 (adding to keyset and | ||
pop again) #2202 | ||
- C++ API for KeySet and Key has unexpected differences: also use ref counting | ||
for KeySets (also suggested in #1332) | ||
|
||
## Constraints | ||
|
||
## Assumptions | ||
|
||
## Considered Alternatives | ||
|
||
- make ref counting thread safe (probably useful for JNI) | ||
- start with 1 for reference counting and let keyDecRef do keyDel | ||
|
||
## Decision | ||
|
||
- add second reference counter to Key | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See #2202: probably it is simply a documentation issue and no second ref counter is needed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Honestly, what was the decision meeting for, if we immediately reverse one of the main decisions? See #3949 (comment) for an idea on how documentation/API naming can make the second ref counter much easier to understand. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
In some cases we also went into timeouts and agreed there would be a follow up.
I like the idea but this immediately raises the question why locking the value or meta-data does not get ref-counted. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
To me the question is: Why is locking value/meta-data a thing at all? Is there a use-case for it? AFAICT we only directly use For the metadata case we should actually check the key name in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It is an invariant of a KeySet that its keys are ordered. The locking ensures this invariant.
The idea was to use this for meta keys. But probably this is now useless, as meta keys can be recognized by the namespace. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, misread. I was thinking about if we can get rid of locking at all. And probably we can, we could simply disallow name changes with ref counter != initial value. To answer your previous question: Locking of values was added for completeness. Other than read-only keys there is probably no usecase. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Then you can't ever change the name from Java or another language with automatic garbage collection, where incrementing the ref counter is required. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As far as i understand:
To better understand the issue:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
In short, to avoid doing some memory allocations. In my case I had a loop where a key was removed and a new one was added with a new basename. The same can be done with immutable keys, but it requires allocating new memory for N keys instead of reusing the old keys. I was just confused that unlocking is currently not possible at all. When a key is added to a keyset the name is locked, but when it is removed from the keyset the name is not unlocked. To be consistent we should either have immutable keys or provide a way to unlock the keys again.
It is required because we use binary search to find a key in the keyset. It is also a nice property and I don't see much reason to rework this, unless there are some huge benefits? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the Hashmap requires the order as well, since it only stores indices into the array. Also
I had the same experience. |
||
- One counter is for locking references, the other one for general references. A locking reference automatically locks/unlocks the keyname. | ||
- introduce reference counter for KeySets (for external keyset references) | ||
- `keyBorrow` returns an error in case of reference counter overflow | ||
- use fixed sized types for reference counters | ||
- increment/decrement references before/after passing instances to plugins | ||
|
||
## Rationale | ||
|
||
- Adding a second reference counter to Key and reducing the size of both significantly (`size_t` to `uint16_t`) | ||
actually saves memory (32 vs 64bit on 64-bit machines) compared to the previous solution. | ||
- The added complexity of maintaining two reference counters is worth the | ||
lawli3t marked this conversation as resolved.
Show resolved
Hide resolved
|
||
tradeoff for the gained functionality. | ||
|
||
## Implications | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Implications should state which code needs to be changed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Still missing. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Still missing. |
||
## Related Decisions | ||
|
||
## Notes |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
# Remove elektraMalloc() et al. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A related question is the documentation of the life-time of our objects. Here also improvements of the docu is needed. |
||
|
||
## Problem | ||
|
||
A big problem with having elektraMalloc is, that it makes it impossible to call | ||
libc functions that allocate data with malloc (e.g. strndup) or others that | ||
expect a pointer that can be passed to realloc. You might even do that by | ||
accident and never notice the problem, until someone replaces elektraMalloc | ||
and then you could get some pretty bad memory bugs. | ||
|
||
Functions considered for removal: | ||
|
||
- `elektraMalloc` | ||
- `elektraCalloc` | ||
- `elektraFree` | ||
- `elektraStrDup` | ||
- `elektraStrLen` | ||
|
||
## Constraints | ||
|
||
Proprietary apps are sometimes delivered together with a libc (either static or | ||
a startup script makes sure their own libs are found). If they would use | ||
Elektra, and the person wants the app to use the global Elektra it is a valid | ||
use case for the dedicated functions. | ||
|
||
## Assumptions | ||
|
||
The only safe way to keep elektraMalloc et al. is to define that they will | ||
always call their libc counterpart (malloc et al.) and their purpose is simply | ||
to add assertions. Then we can also make them private, because you can just | ||
call free to free any pointer returned by Elektra. Ideally, I'd remove the | ||
functions, but it seems unlikely this will be accepted. | ||
|
||
## Considered Alternatives | ||
|
||
- Completely replace `elektraMalloc` / `elektraCalloc` with simple calls to malloc | ||
|
||
## Decision | ||
|
||
- keep current state with the custom functions | ||
- make it optional for plugin developers to use c builtins or the custom Elektra | ||
functions | ||
- Fix places where non-Elektra functions get used | ||
- Remove everything except `elektraFree` from public API | ||
- Remove all functions that don't actually involve any memory allocations (e.g. `elektraStrLen`, `elektraStrCmp`) | ||
- Add `elektraStrNDup` and other `stdlib` equivalents that do memory allocation. | ||
|
||
lawli3t marked this conversation as resolved.
Show resolved
Hide resolved
|
||
## Rationale | ||
|
||
While the current state might cause some problems with Compiler optimizations | ||
and discoverability, those issues probably are only very minor in practice. | ||
Removing all functions poses a considerable amount of work, which would have to | ||
be undone in case we need those functions one day anyway. The current downsides | ||
don't justify such a big procedure. | ||
|
||
## Implications | ||
|
||
## Related Decisions | ||
|
||
## Notes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As this depends on error_handling.md, probably we should also move it to "In Discussion".