Provided by: elektra-doc_0.8.14-5.1ubuntu2_all bug

NAME

       elektra-algorithm - core algorithm of elektra

       You  might  want  to  read about architecture elektra-architecture.md. and data structures
       first elektra-data-structures.md.

Introduction

       In this section, we will explain the heart of Elektra. kdbOpen() is  responsible  for  the
       setup  and  the  construction of the data structures needed later. kdbGet() does, together
       with  the  plugins,  all  actions  necessary  to  read  in  the  configuration.   kdbSet()
       orchestrates  the  plugins  to  write  out the configuration correctly. kdbClose() finally
       frees all previously allocated data structures.

   kdbOpen
       kdbOpen()  retrieves  the  \empha{mount  point  configuration}  with  kdbGet()  using  the
       \empha{default  backend}.  During  this  process, the function sets up the data structures
       which are needed for later invocations of kdbGet() or kdbSet(). All  backends  are  opened
       and  mounted  in  the  appropriate  parts of the key hierarchy. The resulting backends are
       added both to the Split and the Trie object. kdbOpen() finally returns a KDB  object  that
       contains all this information as shown in Figure~\ref{fig:architecture}.

       The reading of the mount point configuration and the consequential self configuring of the
       system is called \intro{bootstrapping}. Elektra builds itself up from the  simple  variant
       with  a  default  backend only to the sophisticated configuration system presented in this
       thesis.

       kdbOpen() creates a Split object. It  adds  all  backend  handles  and  parentKeys  during
       bootstrapping.  So  the buildup of the Split object takes place once. The resulting object
       is then used for both kdbGet() and kdbSet(). This approach is much better testable because
       the  Split  object  is  first initialised using the mount point configuration -- separated
       from the filtering of the backends for every specific kdbGet() and kdbSet() request.

       Afterwards the key hierarchy is static. Every application using Elektra will build up  the
       same  key  database.  Application  specific mount points are prohibited because changes of
       mount points would destroy the global key database. Elektra could not guarantee that every
       application retrieves the same configuration with the same key names any longer.

       In  kdbOpen(),  nearly no checks are done regarding the expected behaviour of the backend.
       The contract checker guarantees that only appropriate mount points are  written  into  the
       mount  point configuration. kdbOpen() checks only if the opening of plugin was successful.
       If not, the backend enclosing the plugin is not mounted at all.

   Removing Keys
       In Elektra version $0.6$, removing keys was an explicit request. Only a single Key  object
       could  be  removed from the database. For configuration files this method is inapplicable.
       For filesys, however, it was easy to implement.

       In Elektra version $0.7$,  the  behaviour  changed.  Removing  keys  was  integrated  into
       kdbSet(). The user tagged keys that should be removed. After the next kdbSet(), these keys
       were removed from the key database. On the one hand, backends writing configuration  files
       simply  ignored  the  keys  marked  for  removal.  On  the other hand, filesys needed that
       information to remove the files. To make this approach work for filesys, the  marked  keys
       were  located  at  the  very  end  of  the  KeySet and sorted in reverse. With this trick,
       recursive removing worked well. But this approach  had  major  defects  in  the  usage  of
       KeySet.  Because  marking  a  key  to  be  removed  changed  the sort order of the key set
       \method{ksLookupByName()} did not find this key anymore.

       So in the present version removing keys  is  consistent  again.  A  KeySet  describes  the
       current  configuration. The user can reduce the KeySet object by \empha[pop]{popping} keys
       out. The kdbSet() function applies exactly this configuration as specified by the key  set
       to  the  key  database.  Contrary to the previous versions, the popped keys of the key set
       will be permanently removed.

       The new circumstance yields idempotent properties for kdbSet(). The  same  KeySet  can  be
       applied  multiple  times,  but  after the first time, the key database will not be changed
       anymore. (Note that kdbSet()) actually detects that there  are  no  changes  and  will  do
       nothing. To actually show the idempotent behaviour the KeySet has to be regenerated or the
       key database needs to be reopened.

       It is, however, not known if keys should be removed permanently only by investigating  the
       KeySet. But only if this knowledge is present, the core can decide if the key set needs to
       be written out or if the configuration is unchanged. So we decided to track how many  keys
       are  delivered in kdbGet(). If the size of the KeySet is lower than this number determined
       at the previous kdbGet(), Elektra´s core knows that some keys were popped. Hence, the next
       kdbSet() invocation needs to change the concerned key database.

       The  situation is now much clearer. The semantics of popping a key will result in removing
       the key from the key database. And the intuitive idea that a KeySet will be applied to the
       key database is correct again.

kdbGet

       It  is  critical  for  application  startup  time to retrieve the configuration as fast as
       possible. Hence, the design goal of the kdbGet() algorithm is to be efficient while  still
       enabling  plugins  to  have  relaxed  postconditions.  To  achieve  this,  the sequence of
       \intro[syscall]{syscalls} must be optimal. On the other hand, it is not tolerable to waste
       time  or  memory  inside  Elektra´s  core, especially during an initial request or when no
       update is available.

       The synopsis of the function is:

           int kdbGet(KDB *handle, KeySet *returned, Key * parentKey);

       The user passes a key set, called returned. If the user invokes kdbGet() the  first  time,
       he  or  she  will  usually  pass  an  empty  key  set.  If  the  user  wants to update the
       application´s settings, returned will typically contain the configuration of the  previous
       kdbGet()  request.  The  parentKey holds the information below which key the configuration
       should be retrieved. The handle contains the data structures  needed  for  the  algorithm,
       like the Split and the Trie objects, as shown in Figure~\ref{fig:architecture}.

       kdbGet()  does  a  rather  easy  job,  because  kdbSet() already guarantees that only well
       formatted, non-corrupted and well-typed configuration is written out in the key  database.
       The  task  is  to  query  all  backends in question for their configuration and then merge
       everything.

   Responsibility
       A backend may yield keys that it is not responsible for. It is not possible for a  backend
       to  know  that  another  backend  has  been  mounted  below  and  the other backend is now
       responsible for some of the keys that are still in the storage. Additionally, plugins  are
       not  able  to  determine  if  they  are responsible for a key or not. Consequently, it can
       happen that more than one backend delivers a key with the same name.

       kdbGet() ensures that a key is uniquely identified by its name. Elektra´s  core  will  pop
       keys  that  are  outside  of  the  backend´s responsibility. Hence, these keys will not be
       passed to the user and we get the desired behaviour: The nearest mounted  backend  to  the
       key is responsible.

       For  example, a generator plugin in the backend A always emits following keys\footnote{(A)
       and (B) indicate from which backend the key  comes  from.}:  \begin{lstlisting}[language=]
       user/sw/generator/akey  (A)  user/sw/generator/dir  (A) user/sw/generator/dir/outside1 (A)
       user/sw/generator/dir/outside2 (A) \end{lstlisting} It will still return these  keys  even
       if  the  plugin  is  not  responsible for some of them anymore. This can happen if another
       backend B is mounted to \keyname{user/sw/generator/dir}. In  the  example  it  yields  the
       following      keys:      \begin{lstlisting}[language=]      user/sw/generator/dir     (B)
       user/sw/generator/dir/new (B) user/sw/generator/dir/outside1 (B) user/sw/generator/outside
       (B)  \end{lstlisting}  In this situation kdbGet() is responsible to pop all three keys at,
       and   below,    \keyname{user/sw/generator/dir}    of    backend    A    and    the    key
       \keyname{user/sw/generator/outside} of backend B. The user will get the resulting key set:
       \begin{lstlisting}[language=]   user/sw/generator/akey   (A)   user/sw/generator/dir   (B)
       user/sw/generator/dir/new  (B)  user/sw/generator/dir/outside1  (B)  \end{lstlisting} Note
       that  the  key  exactly  at  the  mount  point  comes  from   the   backend   mounted   at
       \keyname{user/sw/generator/dir}.

   Sequence
       kdbOpen() already creates a Split object for the whole configuration tree. In this object,
       kdbOpen() will append a list of  all  backends  available.  A  specific  kdbGet()  request
       usually  includes  only  a  part  of  the  configuration.  For  example,  the user is only
       interested  in  keys  below  \keyname{user/sw/apps/userapp}.  All  backends  that   cannot
       contribute  to configuration below \keyname{user/sw/apps/userapp} will be omitted for that
       request. To achieve this, parts of the Split object are filtered out. After this  step  we
       know the list of backends involved. The Split object allocates a key set for each of these
       backends.

       Afterwards the first plugin of each backend is called to determine if an update is needed.
       If no update is needed, the algorithm has finished and returns zero.

       Now  we  know  which  backends  do  not  need  an update. For these backends, the previous
       configuration from returned is appointed from to the key sets of  the  Split  object.  The
       algorithm will not set the \empha{syncbits} of the Split object for these backends because
       the storage of the backends already contains up-to-date configuration.

       The other backends will be requested to \emph{retrieve} their configuration.  The  initial
       empty  KeySet  from  the  Split  object  and  the  relevant  file name in the key value of
       parentKey are passed to each remaining plugin. The plugins extend,  validate  and  process
       the  key  set.  When an error has occurred, the algorithm can stop immediately because the
       user´s KeySet returned is not changed at this point. When this part  finishes,  the  Split
       object contains the whole requested configuration separated in various key sets.

       Subsequently the freshly received keys need some \emph{post-processing}:

       •   Newly  allocated  keys  in  Elektra always have the \empha{sync flag} set. Because the
           plugins allocate and modify keys with the same functions as  the  user,  the  returned
           keys  will  also  have  their  sync  flag  set.  But from the user´s point of view the
           configuration is unmodified. So some code needs to remove this sync flag. To relax the
           post conditions of the plugins, kdbGet() removes it.

       •   To  detect  removed  keys  in  subsequent  kdbSet() calls, kdbGet() needs to store the
           number of received keys of each backend.

       •   Additionally, for every key it is checked if it belongs to this  backend.  This  makes
           sure that every key comes from a single source only as designated by the Trie. In this
           process, all duplicated  and  overlapping  keys  will  be  popped  in  favour  of  the
           responsible backend as described below in responsibility.

       The  last  step  is  to  \emph{merge}  all  these key sets together. This step changes the
       configuration visible to the user. After some cleanup the algorithm finally finishes.

   Updating Configuration
       The user can call kdbGet() often even if the configuration or parts of it are  already  up
       to  date.  This can happen when applications reread configuration in some events. Examples
       are signals\footnote{SIGHUP is the signal used for that on UNIX systems. It is  sent  when
       the program´s controlling terminal is closed. Daemons do not have a terminal so the signal
       is reused for reloading configuration.}, notifications, user requests  and  in  the  worst
       case periodical attempts to reread configuration.

       The  given goal is to keep the sequence of needed syscalls low. If no update is needed, it
       is   sufficient   to   request   the    timestamp\footnote{On    POSIX    systems    using
       \lstinline{stat()}.}  of  every  file.  No  other  syscall is needed. Elektra´s core alone
       cannot check that because getting a timestamp is not defined within the standard  C99.  So
       instead the resolver plugin handles this problem. The resolver plugin returns 0 if nothing
       has changed.

       This decision yields some advantages. Both the storage  plugins  and  Elektra´s  core  can
       conform  to C99. Because the resolver plugin is the very first in the chain of plugins, it
       is guaranteed that no useless work is done.

   Initial kdbGet Problem
       Because Elektra provides self-contained configuration, kdbOpen() has to retrieve  settings
       in  the  \empha{bootstrapping}  process  below  \keyname{system/elektra}  as  explained in
       \secref{bootstrapping}. Because of the  new  way  to  keep  track  of  removed  keys,  the
       internally  executed  kdbGet()  creates  a problem. Without countermeasures even the first
       kdbGet() of a user  requesting  the  configuration  below  \keyname{system/elektra}  fails
       because  the  resolver  finds  out  that  the  configuration  is  already  up to date. The
       configuration delivered by the user is empty  at  this  point.  As  a  result,  the  empty
       configuration will be appointed and returned to the user.

       A  simple  way  to  resolve this issue is to reload the default backend after the internal
       configuration was fetched. Reloading resets the timestamps and kdbGet() works as expected.

kdbSet}

       Not the performance, but robust and reliable behaviour is the  most  important  issue  for
       kdbSet().  The  design  was  chosen  so  that  some  additional  in-memory comparisons are
       preferred to a suboptimal sequence of \empha[syscall]{syscalls}. The algorithm makes  sure
       that  keys  are written out only if it is necessary because applications can call kdbSet()
       with an unchanged KeySet. For the code to decide this, performance is important.

   Properties
       kdbSet() \empha[guarantee]{guarantees} the following properties:

       \begin{enumerate}

       \item Modifications to permanent storage are only made when the configuration was changed.

       \item When errors occur, every plugin gets a chance to rollback its changes  as  described
       in \secref{exception safety}.

       \item  If  every  plugin  does this correctly, the whole KeySet is propagated to permanent
       storage. Otherwise nothing is changed in the key database. Plugins  developed  during  the
       thesis meet this requirement.

       \end{enumerate}

       The synopsis of the function is: \begin{lstlisting} int kdbSet(KDB handle, KeySetreturned,
       Key * parentKey); \end{lstlisting}

       The user passes the configuration using the KeySet returned.  The  key  set  will  not  be
       changed by kdbSet(). The parentKey provides a way to limit which part of the configuration
       is written out.  For  example,  the  parentKey  \keyname{user/sw/apps/myapp}  will  induce
       kdbSet()  to  only modify the key databases below \keyname{user/sw/apps/myapp} even if the
       KeySet returned also contains more configuration. Note that all backends with no  keys  in
       returned but that are below parentKey will completely wipe out their key database. The KDB
       handle contains the necessary data structures as shown in Figure~\ref{fig:architecture}.

       \newpage

   Search for Changes
       \begin{wrapfigure}{r}{0.5\textwidth} \vspace{-40pt} \begin{center} \includegraphics[trim =
       0    65    0    0,   clip=true,   width=6cm]{resolver_set}   \end{center}   \vspace{-20pt}
       \caption{\lstinline{kdbSet()}    Algorithm}    \label{fig:resolver_set}     \vspace{-10pt}
       \end{wrapfigure}

       As  a  first  step, kdbSet() \emph{divides} the configuration passed in by the user to the
       key sets in the Split object. kdbSet() searches for every key if the \empha{sync flag}  is
       checked. Then kdbSet() decides if a key was removed from a backend by comparing the actual
       size of the key set with the size stored from the last kdbGet() call. We see  that  it  is
       necessary to call kdbGet() first before invocations of kdbSet() are allowed.

       We  know  that  data of a backend has to be written out if at least one key was changed or
       removed. If no backend has any changes, the algorithm will terminate at  this  point.  The
       careful reader notices that the process involves no file operations.

   Duplicated Key Sets
       If  some  backends  need  synchronisation,  the  algorithm  continues by filtering out all
       backends in the Split object that do not have changes. At this point, the Split object has
       a list of backends with their respective key sets.

       \label{deep duplicate}

       Plugins  in kdbSet() can change values. Other than in kdbGet(), the user is not interested
       in these changes. Instead, the values are transformed to be suitable for the  storage.  To
       make sure that the changed values are not passed to the user, the algorithm continues with
       a \empha{deep duplication} of all key sets in the Split object.

   Resolver
       All plugins of each included backend are executed one by one up to the resolver plugin. If
       this  succeeds, the resolver plugin is responsible for committing these changes. After the
       successful commit, \empha[error code]{error codes} of plugins are  ignored.  Only  logging
       and notification plugins are affected.

   Atomic Replacement
       For  this  thesis  only  file-based  storage  with  atomic  properties were developed. The
       replacement of a file with another file that has not yet been written is not trivial.  The
       straightforward  way  is  to  lock  a  file and start writing to it. But this approach can
       result in broken or partially finished files in events like ´´out of disc space´´, signals
       or other asynchronous aborts of the program.

       A  temporary  file  solves  this  problem, because in problematic events the original file
       stays untouched. When the temporary file is written out properly, it is  renamed  and  the
       original  configuration file is overwritten. But another concurrent invocation of kdbSet()
       can try to do the same with the result that one of the newly written files is lost.

       To avoid  this  problem,  locks  are  needed  again.  It  is  not  possible  to  lock  the
       configuration  file itself because it will be unlinked when the temporary file is renamed.
       So a third file for locking is needed. The resolver currently implements this approach.

       An alternative to this approach without locks is to completely rely  on  the  modification
       time.  The modification time typically has only a resolution of one second. So any changes
       within that time slot will not be recognised. For this  approach,  however,  the  name  of
       every  temporary  file  must be unique because concurrent kdbSet() invocations each try to
       create one. The temporary file must also be unlinked in case of  a  rollback.  The  opened
       temporary  file  can  be  passed to the storage plugins using a file name in the directory
       \keyname{/dev/fd}. This approach may be more practical than the currently implemented  way
       because it does not need the additional lock file\footnote{Nevertheless, the other way was
       chosen to test if the algorithm  is  exception  safe  as  described  in  \secref{exception
       safety}.}.

   Errors
       The plugins within kdbSet() can fail for a variety of reasons. \intro[conflict]{Conflicts}
       occur most frequently. A conflict means that during executions of  kdbGet()  and  kdbSet()
       another  program  has  changed  the  key database. In order not to lose any data, kdbSet()
       fails without doing anything. In conflict situations  Elektra  leaves  the  programmer  no
       choice.  The programmer has to retrieve the configuration using kdbGet() again to be up to
       date with the key database. Afterwards it  is  up  to  the  application  to  decide  which
       configuration to use. In this situation it is the best to ask the user, by showing him the
       description and reason of the error, how to continue:

       \begin{enumerate}

       \label{conflicts}

       \item Save the configuration again. The changes of the other program will be lost in  this
       case.

       \item  The  key  database  can also be left unchanged as the other program wrote it. After
       using kdbGet() the application is already up to  date  with  the  new  configuration.  All
       configuration changes the user made before will be lost.

       \item  The  application can try to merge the key sets to get the best result. If no key is
       changed on both sides the result is clear, otherwise the application has to decide if  the
       own  or  the other configuration should be favoured. The result of the merged key sets has
       to be written out with kdbSet().

       Merging the key sets can be done with ksAppend(). The source parameter  is  the  preferred
       configuration.  Note  that the downside of the third option is that a merged configuration
       can be an not validating configuration.

       \end{enumerate}

       Sometimes a concrete key causes the problem that the whole key set cannot be stored.  That
       can  happen  on  validation or because of type errors. Such errors are usually caused by a
       mistake made by the user. So the user is responsible for changing the settings to make  it
       valid  again.  In such situations, the \empha{internal cursor} of the KeySet returned will
       point to the problematic key.

       A completely different approach is to export the configuration when kdbSet()  returned  an
       error  code. The user can then edit, change or merge this configuration with more powerful
       tools. Finally, the user can import the configuration into the global  key  database.  The
       export   and   import   mechanism  is  called  ´´streaming´´  and  will  be  explained  in
       \secref{streaming}.

                                            July 2017                        ELEKTRA-ALGORITHM(7)