Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrate kdbEnsure into kdbOpen #3651

Merged
merged 50 commits into from Mar 5, 2021

Conversation

kodebach
Copy link
Member

Implements #2764

Basics

These points need to be fulfilled for every PR:

  • Short descriptions of your changes are in the release notes
    (added as entry in doc/news/_preparation_next_release.md which
    contains _(my name)_)
    Please always add something to the release notes.
  • Details of what you changed are in commit messages
    (first line should have module: short statement syntax)
  • References to issues, e.g. close #X, are in the commit messages.
  • The buildservers are happy. If not, fix in this order:
    • add a line in doc/news/_preparation_next_release.md
    • reformat the code with scripts/dev/reformat-all
    • make all unit tests pass
    • fix all memleaks
  • The PR is rebased with current master.

If you have any troubles fulfilling these criteria, please write
about the trouble as comment in the PR. We will help you.
But we cannot accept PRs that do not fulfill the basics.

Checklist

Check relevant points but please do not remove entries.
For docu fixes, spell checking, and similar none of these points below
need to be checked.

  • I added unit tests for my code
  • I fully described what my PR does in the documentation
    (not in the PR description)
  • I fixed all affected documentation
  • I added code comments, logging, and assertions as appropriate (see Coding Guidelines)
  • I updated all meta data (e.g. README.md of plugins and METADATA.ini)
  • I mentioned every code not directly written by me in THIRD-PARTY-LICENSES

Review

Reviewers will usually check the following:

Labels

If you are already Elektra developer:

  • Add the "work in progress" label if you do not want the PR to be reviewed yet.
  • Add the "ready to merge" label if the basics are fulfilled and you also
    say that everything is ready to be merged.

@kodebach
Copy link
Member Author

I think the new implementation will work something like this:

  • Contract keys are below system:/elektra/contract
  • Each part below system:/elektra/contract/<contract-type> is a separate contract
  • <contract-type> can be anything but, just a few pre-defined values will have an effect
  • The contract-type globalkeyset copies data into the global keyset
    All keys system:/elektra/contract/globalkeyset/... will renamed to system:/elektra/... and copied into the global keyset.
    This will always be the first contract that is handled, since it may contain configuration for later ones.
  • The contract-type mountglobal mounts global plugins
    system:/elektra/contract/mountglobal/<plugin>/ mounts the plugin <plugin> globally with the config below
    Note: the first implementation will delegate to the list plugin here, once global plugins are reworked and list is removed, this will happen directly in kdbOpen
  • Everything else below system:/elektra/contract/ will be ignored for now. Detailed documentation will be in doc/dev/kdb-contracts.md

This is should be enough for both gopts and notifications. The new functions elektraGOptsContract() and elektraNotificationContract() will add contracts for the relevant data (e.g. argv and the ioBinding) below system:/elektra/contract/globalkeyset/ and basic contracts for mounting the plugins below system:/elektra/contract/mountglobal/gopts/ and system:/elektra/contract/mountglobal/internalnotification/ respectively.

@kodebach
Copy link
Member Author

There is now a rough sketch of the implementation, to implementing the comment above.

@lgtm-com
Copy link

lgtm-com bot commented Feb 11, 2021

This pull request introduces 3 alerts when merging f2a2b1e into 77aec77 - view on LGTM.com

new alerts:

  • 3 for FIXME comment

src/plugins/cache/cache.c Outdated Show resolved Hide resolved
src/plugins/cache/cache.c Outdated Show resolved Hide resolved
* i.e. there is no prefix to replace
* @retval 1 if the prefix was sucessfully replaced
*/
int keyReplacePrefix (Key * key, const Key * oldPrefix, const Key * newPrefix)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this function to kdb.h since it is

  1. quite useful to some plugins
  2. much easier to use than manually calling keyName, keySetName, keyNameSize etc.
  3. heavily optimized compared to the alternative
  4. uses many internals of struct _Key that really shouldn't be modified outside the core

* @retval 0 if @p ks contains no keys below @p root (and also not @p root itself)
* @returns otherwise, the number of keys that have been renamed
*/
ssize_t ksRename (KeySet * ks, const Key * root, const Key * newRoot)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could add this function to kdb.h as well, but I put in in kdbprivate.h for now.

Mainly, because of the fact that it modifies keys with key->ksReference == 1 in-place. This is nice, because you can avoid allocating new keys and sometimes even completely avoids re-sorting the KeySet, but it can easily lead to memory bugs. For example in a section like this:

const char * pluginName = keyBaseName (cur);
KeySet * pluginConfig = ksCut (contract, cur);
// increment ref count, because cur is part of pluginConfig and
// we hold a reference to cur that is still needed (via pluginName)
keyIncRef (cur);
ksRename (pluginConfig, cur, pluginConfigRoot);

However, apart from this the function is completely safe to use and could be useful in plugins that move keys around. So maybe putting it into kdb.h could make sense.

* in @p ks, the size of @p ks is returned. The snippet above
* shows why this is useful.
*/
elektraCursor ksFindHierarchy (const KeySet * ks, const Key * root, elektraCursor * end)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found this neat alternative to the ksExtract function we discussed in #2992. I put it into kdbprivate.h for now, but I really think it should be in kdb.h since it will be incredibly useful.

I'll also rewrite elektraKsFindCutpoint (used by ksCut) to use this, instead of just looking for the root and then looping over the following keys.

@kodebach
Copy link
Member Author

@markus2330 The actual integration of kdbEnsure into kdbOpen, i.e. the contracts part, should now be done. I just need to rewrite the notifications stuff and then replace the kdbOpenOld calls (and fix bugs).

@@ -26,8 +26,10 @@ impl Drop for KDB {
impl KDB {
/// Opens the session with the Key database.
pub fn open<'a>() -> Result<Self, KDBError<'a>> {
// FIXME: binding
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@PhilippGackstatter Should I keep anything in mind here, you can I just add the new KeySet parameter? Also would using std::option be a good idea, since the KeySet can be NULL in C?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can just add it. We could do the signature like this:

pub fn open<'a, C>(contract: C) -> Result<Self, KDBError<'a>>
    where
        C: Into<Option<Contract>>;

This way, we can call KDB::open(None) and KDB::open(Contract { ... }) respectively, without having to specify Some(Contract {}) in the second case, so it's a bit nicer than using Option directly.

I tried it here on the Rust playground.

@@ -0,0 +1,87 @@
--[[
-- TODO: cannot call std::vector<std::string> function from lua
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The std::vector<std::string> solution I used for Ruby and Python doesn't work for Lua (it seems Lua Tables can't easily be converted into std::vector). Do we have someone who knows more about Lua and SWIG, or should we just leave elektraGOptsContract unsupported in Lua?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@manuelm as you are our best Lua hacker, do you have some idea how this can be done?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should work out of the box:

local custom_argv = kdb.StringVector()
custom_argv:push_back("test")
custom_argv:push_back("get")
custom_argv:push_back("-v")
custom_argv:push_back("user:/")

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I already found a different solution, but thanks for the help

@kodebach
Copy link
Member Author

@markus2330 Apart from the updates to the Java and the Rust binding (and the public API questions) this PR should be done, please Review (or delegate to the appropriate person)

Copy link
Contributor

@markus2330 markus2330 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amazing work, I really like your attention to many details and the many other small things that you fixed.

The main issues are (maybe not so obvious from the individual comments):

  1. there is a bit chaos about the contract functions: in kdb.h they are called elektra.* but in other places it is in KDB namespace/class.
  2. modularization not ideal: *Contract functions should be (from modularity point of view) ideally be part of the module, not of the core. Ideally applications should even link against libelektra-notification (in this case they actually already do) or libelektra-opts, then the contract could also be part of that lib/plugin.
  3. global keyset is not documented but with this PR many semantics of the global keyset were actually assumed/fixed.

Btw. are opts/gotps really the ideal name? Why do we have a src/libs/opts/opts.c library if it is not user-facing? Is this something that actually should be static? (see also point 2. about modularization)

doc/dev/kdb-contracts.md Outdated Show resolved Hide resolved
doc/dev/kdb-contracts.md Outdated Show resolved Hide resolved
doc/dev/kdb-contracts.md Show resolved Hide resolved
doc/news/_preparation_next_release.md Outdated Show resolved Hide resolved
doc/news/_preparation_next_release.md Outdated Show resolved Hide resolved
src/include/kdb.h.in Outdated Show resolved Hide resolved
src/libs/elektra/kdb.c Show resolved Hide resolved
src/libs/highlevel/elektra.c Show resolved Hide resolved
doc/news/_preparation_next_release.md Outdated Show resolved Hide resolved
@kodebach
Copy link
Member Author

  1. there is a bit chaos about the contract functions: in kdb.h they are called elektra.* but in other places it is in KDB namespace/class.

C++ uses the kdb namespace for everything (including KeySet and Key). SWIG uses a global kdb object to wrap this namespace. I'm not sure where you'd want to put the function? A new elektra namespace (AFAIK we already use that for the highlevel API)?

In Java everything has to be in a class. We could create a new KDBContracts class, but why not just leave it in KDB? After all you will use these functions together with kdbOpen.

I didn't call it e.g. kdbGOptsContract or kdbContractGOpts in C, because it's not part of the core KDB API and our helper functions normally use the prefix elektra.

  1. modularization not ideal: *Contract functions should be (from modularity point of view) ideally be part of the module, not of the core. Ideally applications should even link against libelektra-notification (in this case they actually already do) or libelektra-opts, then the contract could also be part of that lib/plugin.

Unless I made a mistake, the elektraIoContract and elektraNotificationsContract functions should be part of libelektra-io and libelektra-notification respectively. However, elektraGOptsContract is part of the core, because it actually works without explicitly linking against libelektra-opts (since the gopts plugin does the linking). Another major reason why I put it into the core, is that it makes the SWIG bindings much easier. AFAIK we would either always have to link against libelektra-opts in SWIG or create a separate binding just for elektraGOptsContract. The C++ binding with it's header only setup could also be problematic.

  1. global keyset is not documented but with this PR many semantics of the global keyset were actually assumed/fixed.

Yes, we should actually fix that. I didn't change any of the global keyset behaviour AFAIK (apart from not caching everything). Basically, everything this PR depends on related to the global keyset, is stuff the cache and mmapstorage plugin also depend on.

Btw. are opts/gotps really the ideal name? Why do we have a src/libs/opts/opts.c library if it is not user-facing? Is this something that actually should be static? (see also point 2. about modularization)

The name is just a name. If you have a better idea, feel free to change it.

src/libs/opts/opts.c is part of libelektra-opts which can be used without the gopts plugin as well, by calling elektraGetOpts directly. It doesn't work very well with specifications (other than the opts stuff), since you can't call it before validation plugins are executed, but for simple applications that just want to use the command line parser, it is an option.

@kodebach
Copy link
Member Author

BTW I have no idea what is going on with the failing macOS build jobs. I assume it's the same problem as with the failing Jenkins build, but somehow it doesn't segfault on macOS. However, I have absolutely no clue how to reproduce this. The only thing I'm pretty sure about is that only the Java test has a problem and the other failed tests are because the Java test aborted and didn't clean up properly.

@markus2330
Copy link
Contributor

Yes, we should actually fix that. I didn't change any of the global keyset behaviour AFAIK (apart from not caching everything).

And this is a huge change and must be documented. And afaik this is the only semantic the content of the global keyset has so far, so the docu does not need to include much else. Nobody can read your mind, so please write down how you were thinking how it should be used. (I could only copy texts from your discussion but this leads to very poor docu.)

The name is just a name. If you have a better idea, feel free to change it.

You are obviously very efficient in renaming, most other people, including me, are not. And once users start using it at wider scale, we can forget all about renaming. I already stated that ideally there would be only one interface, which could be called "proc" (like the namespace), so there would be no need for "opts" or "gopts".

Btw. it is not like that I cannot live with the current situation, I understand you if you say my suggestions are too much effort to implement. I just gave the feedback the the API situation of opts/gopts looks to me hackish and historically grown.

@kodebach
Copy link
Member Author

Nobody can read your mind, so please write down how you were thinking how it should be used.

I can't read @mpranj's mind either. So I don't actually know what the original intention behind the global keyset was. I'll write down what I know, but I can't guarantee it will be 100% correct.

I already stated that ideally there would be only one interface,

Yes, ideally both the plugin and the library would use the same name. But that's not possible right now. There is definitely a name collision somewhere in CMake, but there might be other things that prevent this.

The actual interface is pretty much the same already. The command-line argument and environment variable parsing is configured via the metadata in the spec:/ namespace, no matter whether you use elektraGetOpts directly or the gopts plugin.

which could be called "proc" (like the namespace), so there would be no need for "opts" or "gopts".

I think proc is too general, maybe args but that's basically the same as opts.

@markus2330
Copy link
Contributor

I can't read @mpranj's mind either. So I don't actually know what the original intention behind the global keyset was. I'll write down what I know, but I can't guarantee it will be 100% correct.

Thank you very much 💖

I think proc is too general, maybe args but that's basically the same as opts.

If the name proc is too general we also have a problem with the namespace proc. I do not see how args/arguments/variables/options are less general, though.

@kodebach
Copy link
Member Author

If the name proc is too general we also have a problem with the namespace proc.

The namespace proc is the reason why "proc" is too general as a new name for the opts library or the gopts plugin. The proc namespace encompasses much more than just command-line arguments and environment variables. Sure right now that's the only thing we use it for, but the proc namespace is intended for process internal data. That could be a lot more.

I do not see how args/arguments/variables/options are less general, though.

args and opts are still very general, but at least the are limited to what the library/plugin actually does.

@mpranj
Copy link
Member

mpranj commented Feb 18, 2021

I can't read @mpranj's mind either. So I don't actually know what the original intention behind the global keyset was.

Unfortunately I forgot to document that the global keyset is cached. Other than that the intention and usage is documented (in multiple places).

I did not think anybody would want to store pointers in the global keyset. Actually I think the way we store pointers in KeySets (at several places) is abuse of the API and not recommended for any external user/developer.

On the other hand it made sense to cache the global keyset. With the cache we try to store the complete/relevant state of the KDB. The Split for example is also restored from the cache.

@kodebach
Copy link
Member Author

kodebach commented Mar 3, 2021

The Jenkins build should™ now work. The failing job didn't build the gopts plugin and the SWIG and JNA binding tests didn't check for that. (Note: the solution for JNA is a bit more janky, because our integration between CMake and Maven is not great)

I still don't know why the 🍎 MMap job failed, but maybe it now works too. But looking at the Cirrus config, it should build gopts and therefore probably has a different problem.

PS. No, I don't know why the tests segfaulted because of a missing plugin instead of returning the proper error.

This was referenced Mar 3, 2021
@kodebach
Copy link
Member Author

kodebach commented Mar 3, 2021

@mpranj I found out why the "🍎 MMap" job doesn't work. It actually has nothing to do with macOS and happens on Linux as well (with the CMake config from the Cirrus job). The immediate cause is that this line

ksClose (global); // TODO: if we have a global keyset, maybe use ksAppend?

clears the KeySet and removes the data that gopts expects. There is already a TODO here that seems to anticipate the problem, so how should I solve this?

EDIT: I tried a simple solution with ksNew and ksAppend, but immediately got some memory errors. I'll wait for suggestions before investigating further.

@mpranj
Copy link
Member

mpranj commented Mar 3, 2021

There is already a TODO here that seems to anticipate the problem, so how should I solve this?

I can make a PR tomorrow at latest. If it does not break anything else it should be quick.

@kodebach
Copy link
Member Author

kodebach commented Mar 3, 2021

You should be able to push directly to this branch. Then we'd know right away that it fixes the issue as well as not breaking anything else.

@markus2330
Copy link
Contributor

@mpranj thank you very much for helping out here!

@mpranj
Copy link
Member

mpranj commented Mar 4, 2021

@kodebach sorry you hit this bug/nasty leftover TODO, but thank you very much for reporting and pinning down the exact problem.

I've pushed a fix, let's hope the build servers are happy. Works on my machine(tm).

@kodebach
Copy link
Member Author

kodebach commented Mar 4, 2021

From my side this PR is done and the build servers are happy as well. @markus2330 Do still have any input or can this be merged?

@kodebach kodebach mentioned this pull request Mar 4, 2021
14 tasks
@markus2330 markus2330 merged commit a80240f into ElektraInitiative:master Mar 5, 2021
@markus2330
Copy link
Contributor

Thank you so much for this amazing work! So let us test this and release it asap.

@tucek please make sure to rebase to master so that your work is based on top of this (it contains changes to the Java binding).

@tucek
Copy link
Contributor

tucek commented Mar 5, 2021

After rebase (and rebuilding elektra) i get the following test error:
jna_test_failure.txt

Does anyone recognize the Problem?

@kodebach
Copy link
Member Author

kodebach commented Mar 5, 2021

Have you tried doing a clean build? i.e. deleting the whole build directory and calling cmake again (just deleting the src/bindings part of the build directory might work too).

The JNA setup is a bit funky and doesn't really detect, if the C API changed and it should rebuild the JNA bindings. It then tries to call functions that no longer exist, when you run the tests.

@tucek
Copy link
Contributor

tucek commented Mar 5, 2021

i had some so mismatch... i had to first uninstall and then reinstall elektra... thx

@kodebach kodebach mentioned this pull request Mar 20, 2021
20 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

inject argc, argv, environ
7 participants