Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cf-reactor/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ noinst_LTLIBRARIES = libcf-reactor.la
AM_CPPFLAGS = -I$(srcdir)/../libpromises -I$(srcdir)/../libntech/libutils \
-I$(srcdir)/../libcfecompat \
-I$(srcdir)/../libcfnet \
-I$(srcdir)/../libenv \
-I$(srcdir)/../cf-agent \
$(OPENSSL_CPPFLAGS) \
$(PCRE2_CPPFLAGS) \
Expand Down
19 changes: 19 additions & 0 deletions cf-reactor/reactor_transform.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
#include <attributes.h>
#include <conn_cache.h> // ConnCache_Init(), ConnCache_Destroy()
#include <mod_custom.h> // Initialize/FinalizeCustomPromises()
#include <timeout.h> // SetReferenceTime()
#include <time_classes.h> // UpdateTimeClasses()

/* Promise types evaluated within `bundle reactor NAME { ... }`. */
static const char *const REACTOR_TYPESEQUENCE[] =
Expand Down Expand Up @@ -258,13 +260,25 @@ void HandleReactorEvent(EvalContext *ctx, const Promise *pp, const char *promise
assert(pp != NULL);
assert(promiser != NULL);

/* cf-reactor keeps the same EvalContext across events. Like an agent run,
* the bundle runs see the time classes of now and the persistent classes
* that have not expired. The classes and variables they define, cancel or
* change are undone once the event is handled, except the persistent
* classes, which are kept in the state database. */
UpdateTimeClasses(ctx, SetReferenceTime());
ClassTable *classes = NULL;
VariableTable *variables = NULL;
EvalContextSnapshotTake(ctx, &classes, &variables);

ReactorEventParam event = { .promiser = promiser };

EvalContextStackPushBundleFrame(ctx, PromiseGetBundle(pp), NULL, false, NULL);
EvalContextStackPushBundleSectionFrame(ctx, pp->parent_section);
ExpandPromise(ctx, pp, KeepEventsPromiseOnEvent, &event);
EvalContextStackPopFrame(ctx); /* bundle section */
EvalContextStackPopFrame(ctx); /* bundle */

EvalContextSnapshotRestore(ctx, classes, variables);
}

void KeepReactorPromises(EvalContext *ctx, const Policy *policy)
Expand All @@ -289,4 +303,9 @@ void KeepReactorPromises(EvalContext *ctx, const Policy *policy)

EvaluateReactorBundle(ctx, bp);
}

/* Tag the persistent classes the evaluation defined 'source=persistent',
* so that the snapshots taken on events tell them apart even once another
* agent deleted them from the state database, see EvalContextSnapshotTake() */
EvalContextHeapPersistentLoadAll(ctx);
}
20 changes: 20 additions & 0 deletions libpromises/class.c
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,26 @@ bool ClassTableClear(ClassTable *table)
return has_classes;
}

ClassTable *ClassTableCopy(const ClassTable *table)
{
assert(table != NULL);

ClassTable *copy = ClassTableNew();

ClassTableIterator *iter = ClassTableIteratorNew(table, NULL, true, true);
for (const Class *cls = ClassTableIteratorNext(iter); cls != NULL;
cls = ClassTableIteratorNext(iter))
{
/* The tags of a class are never NULL, see ClassInit() */
StringSet *tags = StringSetNew();
StringSetJoin(tags, cls->tags, xstrdup);
ClassTablePut(copy, cls->ns, cls->name, cls->is_soft, cls->scope, tags, cls->comment);
}
ClassTableIteratorDestroy(iter);

return copy;
}

ClassTableIterator *ClassTableIteratorNew(const ClassTable *table,
const char *ns,
bool is_hard, bool is_soft)
Expand Down
6 changes: 6 additions & 0 deletions libpromises/class.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ bool ClassTableRemove(ClassTable *table, const char *ns, const char *name);

bool ClassTableClear(ClassTable *table);

/**
* @brief Deep copy of the table: all its classes, hard and soft, with their
* tags and comments. The copy is owned by the caller.
*/
ClassTable *ClassTableCopy(const ClassTable *table);

ClassTableIterator *ClassTableIteratorNew(const ClassTable *table, const char *ns, bool is_hard, bool is_soft);
Class *ClassTableIteratorNext(ClassTableIterator *iter);
void ClassTableIteratorDestroy(ClassTableIterator *iter);
Expand Down
99 changes: 99 additions & 0 deletions libpromises/eval_context.c
Original file line number Diff line number Diff line change
Expand Up @@ -893,6 +893,105 @@ void EvalContextHeapPersistentLoadAll(EvalContext *ctx)
CloseDB(dbp);
}

/*****************************************************************************/

/* Whether the class is persistent, expired or not: loaded from the state
* database, which tags it 'source=persistent', or defined by a persistent
* class promise, which saves it in the database without tagging it. The keys
* are the class names as ClassRefToString() makes them, see
* EvalContextHeapPersistentSave(). dbp may be NULL if the database cannot be
* opened. */
static bool IsPersistentClass(CF_DB *dbp, const Class *cls)
{
assert(cls != NULL);

if (StringSetContains(cls->tags, "source=persistent"))
{
return true;
}
if (dbp == NULL)
{
return false;
}

char *key = ClassRefToString(cls->ns, cls->name);
const bool persistent = HasKeyDB(dbp, key, strlen(key) + 1);
free(key);
return persistent;
}

void EvalContextSnapshotTake(EvalContext *ctx, ClassTable **classes, VariableTable **variables)
{
assert(ctx != NULL);
assert(classes != NULL);
assert(variables != NULL);

/* 1. Copy the global classes and variables, the hard classes and the
* special variables too. The bundle classes and variables are not in
* the global tables, they disappear with the frame of their bundle. */
*classes = ClassTableCopy(ctx->global_classes);
*variables = VariableTableCopy(ctx->global_variables);

/* 2. Remove the persistent classes, expired or not, from both the copy and
* the context: their lifetime is their TTL in the state database, not
* the snapshot nor the context, which may have outlived it. Collect
* first, the context must not change while being iterated. */
CF_DB *dbp;
if (!OpenDB(&dbp, dbid_state))
{
dbp = NULL;
}

Seq *persistent_classes = SeqNew(16, free);
/* ClassTableIteratorNext() does not filter the hard classes out */
ClassTableIterator *iter = ClassTableIteratorNew(ctx->global_classes, NULL, false, true);
for (const Class *cls = ClassTableIteratorNext(iter); cls != NULL;
cls = ClassTableIteratorNext(iter))
{
if (cls->is_soft && IsPersistentClass(dbp, cls))
{
ClassTableRemove(*classes, cls->ns, cls->name);
SeqAppend(persistent_classes, ClassRefToString(cls->ns, cls->name));
}
}
ClassTableIteratorDestroy(iter);

if (dbp != NULL)
{
CloseDB(dbp);
}

for (size_t i = 0; i < SeqLength(persistent_classes); i++)
{
ClassRef ref = ClassRefParse(SeqAt(persistent_classes, i));
ClassTableRemove(ctx->global_classes, ref.ns, ref.name);
ClassRefDestroy(ref);
}
SeqDestroy(persistent_classes);

/* 3. Load the persistent classes that have not expired into the context,
* like an agent does when it starts. The expired ones are deleted from
* the database instead. */
EvalContextHeapPersistentLoadAll(ctx);
}

void EvalContextSnapshotRestore(EvalContext *ctx, ClassTable *classes, VariableTable *variables)
{
assert(ctx != NULL);
assert(classes != NULL);
assert(variables != NULL);

/* Replace the global classes and variables with the snapshot, which
* discards the ones defined, cancelled or changed since, the persistent
* classes too: the next snapshot loads them again from the state
* database, see EvalContextSnapshotTake(). */
ClassTableDestroy(ctx->global_classes);
ctx->global_classes = classes;

VariableTableDestroy(ctx->global_variables);
ctx->global_variables = variables;
}

void EvalContextSetNegatedClasses(EvalContext *ctx, StringSet *negated_classes)
{
assert(ctx != NULL);
Expand Down
24 changes: 24 additions & 0 deletions libpromises/eval_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,30 @@ void EvalContextHeapPersistentSave(EvalContext *ctx, const char *name, unsigned
void EvalContextHeapPersistentRemove(const char *context);
void EvalContextHeapPersistentLoadAll(EvalContext *ctx);

/**
* @brief Save a copy of the global classes, except the persistent ones, and
* of the global variables, so that EvalContextSnapshotRestore() can
* undo what is evaluated in between. Then reset the persistent classes
* of the context to the ones in the state database that have not
* expired, as an agent starting a run does.
*
* @param classes set to the copy of the classes, owned by the caller
* @param variables set to the copy of the variables, owned by the caller
* @note The copied variables refer to the promises of the policy, so the
* snapshot must be restored (or destroyed) before the policy is.
*/
void EvalContextSnapshotTake(EvalContext *ctx, ClassTable **classes, VariableTable **variables);

/**
* @brief Replace the global classes and variables with the snapshot taken by
* EvalContextSnapshotTake(). The persistent classes are not in it, the
* next snapshot loads them again from the state database.
*
* @param classes the copy of the classes, owned by the context afterwards
* @param variables the copy of the variables, owned by the context afterwards
*/
void EvalContextSnapshotRestore(EvalContext *ctx, ClassTable *classes, VariableTable *variables);

void EvalContextOverrideImmutableSet(EvalContext *ctx, bool should_override);
bool EvalContextOverrideImmutableGet(EvalContext *ctx);

Expand Down
26 changes: 26 additions & 0 deletions libpromises/variable.c
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,32 @@ bool VariableTablePut(VariableTable *table, const VarRef *ref,
return VarMapInsert(table->vars, var->ref, var);
}

VariableTable *VariableTableCopy(const VariableTable *table)
{
assert(table != NULL);

VariableTable *copy = VariableTableNew();

VariableTableIterator *iter = VariableTableIteratorNew(table, NULL, NULL, NULL);
for (const Variable *var = VariableTableIteratorNext(iter); var != NULL;
var = VariableTableIteratorNext(iter))
{
/* VariableTablePut() copies the value, but takes the tags and the
* comment. The tags may be NULL. */
StringSet *tags = NULL;
if (var->tags != NULL)
{
tags = StringSetNew();
StringSetJoin(tags, var->tags, xstrdup);
}
VariableTablePut(copy, var->ref, &var->rval, var->type, tags,
SafeStringDuplicate(var->comment), var->promise);
}
VariableTableIteratorDestroy(iter);

return copy;
}

bool VariableTableClear(VariableTable *table, const char *ns, const char *scope, const char *lval)
{
const size_t vars_num = VarMapSize(table->vars);
Expand Down
6 changes: 6 additions & 0 deletions libpromises/variable.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ bool VariableTableRemove(VariableTable *table, const VarRef *ref);
size_t VariableTableCount(const VariableTable *table, const char *ns, const char *scope, const char *lval);
bool VariableTableClear(VariableTable *table, const char *ns, const char *scope, const char *lval);

/**
* @brief Deep copy of the table, with all its variables. The copied variables
* refer to the same promises.
*/
VariableTable *VariableTableCopy(const VariableTable *table);

VariableTableIterator *VariableTableIteratorNew(const VariableTable *table, const char *ns, const char *scope, const char *lval);
VariableTableIterator *VariableTableIteratorNewFromVarRef(const VariableTable *table, const VarRef *ref);
Variable *VariableTableIteratorNext(VariableTableIterator *iter);
Expand Down
Loading