Explorar o código

ncd: modules: add depend_scope (like multidepend but with separate dependency scopes)

ambrop7 %!s(int64=13) %!d(string=hai) anos
pai
achega
a6af9a3364

+ 1 - 0
blog_channels.txt

@@ -134,3 +134,4 @@ ncd_timer 4
 ncd_file_open 4
 ncd_backtrack 4
 ncd_socket 4
+ncd_depend_scope 4

+ 4 - 0
generated/blog_channel_ncd_depend_scope.h

@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_depend_scope

+ 2 - 1
generated/blog_channels_defines.h

@@ -134,4 +134,5 @@
 #define BLOG_CHANNEL_ncd_file_open 133
 #define BLOG_CHANNEL_ncd_backtrack 134
 #define BLOG_CHANNEL_ncd_socket 135
-#define BLOG_NUM_CHANNELS 136
+#define BLOG_CHANNEL_ncd_depend_scope 136
+#define BLOG_NUM_CHANNELS 137

+ 1 - 0
generated/blog_channels_list.h

@@ -134,3 +134,4 @@
 {"ncd_file_open", 4},
 {"ncd_backtrack", 4},
 {"ncd_socket", 4},
+{"ncd_depend_scope", 4},

+ 1 - 0
ncd/CMakeLists.txt

@@ -171,6 +171,7 @@ add_library(ncdinterpreter
     modules/timer.c
     modules/file_open.c
     modules/backtrack.c
+    modules/depend_scope.c
     ${NCD_ADDITIONAL_SOURCES}
 )
 target_link_libraries(ncdinterpreter

+ 466 - 0
ncd/modules/depend_scope.c

@@ -0,0 +1,466 @@
+/**
+ * @file depend_scope.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * 
+ * @section DESCRIPTION
+ * 
+ * Multiple-option dependencies module.
+ * 
+ * Synopsis:
+ *   depend_scope()
+ * 
+ * Description:
+ *   A scope for dependency names. Any dependency names used in provide() and depend()
+ *   methods on this object are local to this object. Contrast to the multidepend module,
+ *   which provides the same functionality as this module, but with a single global
+ *   dependency name scope.
+ * 
+ * Synopsis:
+ *   depend_scope::provide(name)
+ * 
+ * Arguments:
+ *   name - provider identifier
+ * 
+ * Description:
+ *   Satisfies a dependency.
+ *   If any depend()'s get immediately bound to this provide(),
+ *   the side effects of those first happen, and only then can the process of this
+ *   provide() continue.
+ *   When provide() is requested to deinitialize, if there are any depend()'s bound,
+ *   provide() will not finish deinitializing until all the processes containing the
+ *   bound depend()'s have backtracked to the point of the corresponding depend().
+ *   BUG: Currently, when backtracking finishes for a depend(), the depend() will first
+ *   attempt to rebind itself and only after the immediate effects of the rebinding
+ *   will the provide() to which is was originally bound finish deinitizing. This may
+ *   result in spurious rebindings that are lost shortly after being bound.
+ *   SHOULD BE: When backtracking finishes for a depend(), the immediate effects of the
+ *   originally bound provide() deinitializing will first happen, and only then will
+ *   the depend() attempt to rebind itself.
+ * 
+ * Synopsis:
+ *   depend_scope::depend(list names)
+ * 
+ * Arguments:
+ *   names - list of provider identifiers. Names more to the beginning are considered
+ *     more desirable.
+ * 
+ * Description:
+ *   Binds to the provide() providing one of the specified dependency names which is most
+ *   desirable. If there is no provide() providing any of the given dependency names,
+ *   waits and binds when one becomes available. 
+ *   If the depend() is bound to a provide(), and the bound provide() is requested to
+ *   deinitize, or a more desirable provide() becomes available, the depend() statement
+ *   will go down (triggering backtracking), wait for backtracking to finish, and then
+ *   try to bind to a provide() again, as if it was just initialized.
+ *   When depend() is requested to deinitialize, it deinitializes immediately.
+ * 
+ * Attributes:
+ *   Exposes objects as seen from the corresponding provide.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <misc/offset.h>
+#include <misc/debug.h>
+#include <misc/balloc.h>
+#include <structure/LinkedList1.h>
+#include <ncd/NCDModule.h>
+#include <ncd/NCDRefTarget.h>
+
+#include <generated/blog_channel_ncd_depend_scope.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+struct scope {
+    NCDRefTarget ref_target;
+    LinkedList1 provides_list;
+    LinkedList1 depends_list;
+};
+
+struct scope_instance {
+    NCDModuleInst *i;
+    struct scope *scope;
+};
+
+struct provide {
+    NCDModuleInst *i;
+    struct scope *scope;
+    NCDValRef name;
+    LinkedList1Node provides_list_node;
+    LinkedList1 depends_list;
+    int dying;
+};
+
+struct depend {
+    NCDModuleInst *i;
+    struct scope *scope;
+    NCDValRef names;
+    LinkedList1Node depends_list_node;
+    struct provide *provide;
+    LinkedList1Node provide_depends_list_node;
+    int provide_collapsing;
+};
+
+static struct provide * find_provide (struct scope *o, NCDValRef name)
+{
+    for (LinkedList1Node *ln = LinkedList1_GetFirst(&o->provides_list); ln; ln = LinkedList1Node_Next(ln)) {
+        struct provide *provide = UPPER_OBJECT(ln, struct provide, provides_list_node);
+        ASSERT(provide->scope == o)
+        if (NCDVal_Compare(provide->name, name) == 0) {
+            return provide;
+        }
+    }
+    
+    return NULL;
+}
+
+static struct provide * depend_find_best_provide (struct depend *o)
+{
+    size_t count = NCDVal_ListCount(o->names);
+    
+    for (size_t j = 0; j < count; j++) {
+        NCDValRef name = NCDVal_ListGet(o->names, j);
+        struct provide *provide = find_provide(o->scope, name);
+        if (provide && !provide->dying) {
+            return provide;
+        }
+    }
+    
+    return NULL;
+}
+
+static void depend_update (struct depend *o)
+{
+    // if we're collapsing, do nothing
+    if (o->provide && o->provide_collapsing) {
+        return;
+    }
+    
+    // find best provide
+    struct provide *best_provide = depend_find_best_provide(o);
+    ASSERT(!best_provide || !best_provide->dying)
+    
+    // has anything changed?
+    if (best_provide == o->provide) {
+        return;
+    }
+    
+    if (o->provide) {
+        // set collapsing
+        o->provide_collapsing = 1;
+        
+        // signal down
+        NCDModuleInst_Backend_Down(o->i);
+    } else {
+        // insert to provide's list
+        LinkedList1_Append(&best_provide->depends_list, &o->provide_depends_list_node);
+        
+        // set not collapsing
+        o->provide_collapsing = 0;
+        
+        // set provide
+        o->provide = best_provide;
+        
+        // signal up
+        NCDModuleInst_Backend_Up(o->i);
+    }
+}
+
+static void scope_ref_target_func_release (NCDRefTarget *ref_target)
+{
+    struct scope *o = UPPER_OBJECT(ref_target, struct scope, ref_target);
+    ASSERT(LinkedList1_IsEmpty(&o->provides_list))
+    ASSERT(LinkedList1_IsEmpty(&o->depends_list))
+    
+    BFree(o);
+}
+
+static void scope_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct scope_instance *o = vo;
+    o->i = i;
+    
+    // pass scope instance pointer to methods not NCDModuleInst pointer
+    NCDModuleInst_Backend_PassMemToMethods(i);
+    
+    // read arguments
+    if (!NCDVal_ListRead(params->args, 0)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    // allocate scope
+    o->scope = BAlloc(sizeof(*o->scope));
+    if (!o->scope) {
+        ModuleLog(i, BLOG_ERROR, "BAlloc failed");
+        goto fail0;
+    }
+    
+    // init reference target
+    NCDRefTarget_Init(&o->scope->ref_target, scope_ref_target_func_release);
+    
+    // init provide and depend lists
+    LinkedList1_Init(&o->scope->provides_list);
+    LinkedList1_Init(&o->scope->depends_list);
+    
+    // go up
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_SetError(i);
+    NCDModuleInst_Backend_Dead(i);
+}
+
+static void scope_func_die (void *vo)
+{
+    struct scope_instance *o = vo;
+    
+    // release scope reference
+    NCDRefTarget_Deref(&o->scope->ref_target);
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static void provide_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct provide *o = vo;
+    o->i = i;
+    o->scope = ((struct scope_instance *)params->method_user)->scope;
+    
+    // read arguments
+    NCDValRef name_arg;
+    if (!NCDVal_ListRead(params->args, 1, &name_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    // remember name
+    o->name = name_arg;
+    
+    // check for existing provide with this name
+    if (find_provide(o->scope, o->name)) {
+        ModuleLog(o->i, BLOG_ERROR, "a provide with this name already exists");
+        goto fail0;
+    }
+    
+    // grab scope reference
+    if (!NCDRefTarget_Ref(&o->scope->ref_target)) {
+        ModuleLog(o->i, BLOG_ERROR, "NCDRefTarget_Ref failed");
+        goto fail0;
+    }
+    
+    // insert to provides list
+    LinkedList1_Append(&o->scope->provides_list, &o->provides_list_node);
+    
+    // init depends list
+    LinkedList1_Init(&o->depends_list);
+    
+    // set not dying
+    o->dying = 0;
+    
+    // signal up.
+    // This comes above the loop which follows, so that effects on related depend statements are
+    // computed before this process advances, avoiding problems like failed variable resolutions.
+    NCDModuleInst_Backend_Up(o->i);
+    
+    // update depends
+    for (LinkedList1Node *ln = LinkedList1_GetFirst(&o->scope->depends_list); ln; ln = LinkedList1Node_Next(ln)) {
+        struct depend *depend = UPPER_OBJECT(ln, struct depend, depends_list_node);
+        depend_update(depend);
+    }
+    
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_SetError(i);
+    NCDModuleInst_Backend_Dead(i);
+}
+
+static void provide_free (struct provide *o)
+{
+    ASSERT(LinkedList1_IsEmpty(&o->depends_list))
+    
+    // remove from provides list
+    LinkedList1_Remove(&o->scope->provides_list, &o->provides_list_node);
+    
+    // release scope reference
+    NCDRefTarget_Deref(&o->scope->ref_target);
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static void provide_func_die (void *vo)
+{
+    struct provide *o = vo;
+    ASSERT(!o->dying)
+    
+    // if we have no depends, die immediately
+    if (LinkedList1_IsEmpty(&o->depends_list)) {
+        provide_free(o);
+        return;
+    }
+    
+    // set dying
+    o->dying = 1;
+    
+    // start collapsing our depends
+    for (LinkedList1Node *ln = LinkedList1_GetFirst(&o->depends_list); ln; ln = LinkedList1Node_Next(ln)) {
+        struct depend *depend = UPPER_OBJECT(ln, struct depend, provide_depends_list_node);
+        ASSERT(depend->provide == o)
+        
+        // update depend to make sure it is collapsing
+        depend_update(depend);
+    }
+}
+
+static void depend_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct depend *o = vo;
+    o->i = i;
+    o->scope = ((struct scope_instance *)params->method_user)->scope;
+    
+    // read arguments
+    NCDValRef names_arg;
+    if (!NCDVal_ListRead(params->args, 1, &names_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsList(names_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // remember names
+    o->names = names_arg;
+    
+    // grab scope reference
+    if (!NCDRefTarget_Ref(&o->scope->ref_target)) {
+        ModuleLog(o->i, BLOG_ERROR, "NCDRefTarget_Ref failed");
+        goto fail0;
+    }
+    
+    // insert to depends list
+    LinkedList1_Append(&o->scope->depends_list, &o->depends_list_node);
+    
+    // set no provide
+    o->provide = NULL;
+    
+    // update
+    depend_update(o);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_SetError(i);
+    NCDModuleInst_Backend_Dead(i);
+}
+
+static void depend_func_die (void *vo)
+{
+    struct depend *o = vo;
+    
+    if (o->provide) {
+        // remove from provide's list
+        LinkedList1_Remove(&o->provide->depends_list, &o->provide_depends_list_node);
+        
+        // if provide is dying and is empty, let it die
+        if (o->provide->dying && LinkedList1_IsEmpty(&o->provide->depends_list)) {
+            provide_free(o->provide);
+        }
+    }
+    
+    // remove from depends list
+    LinkedList1_Remove(&o->scope->depends_list, &o->depends_list_node);
+    
+    // release scope reference
+    NCDRefTarget_Deref(&o->scope->ref_target);
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static void depend_func_clean (void *vo)
+{
+    struct depend *o = vo;
+    
+    if (!(o->provide && o->provide_collapsing)) {
+        return;
+    }
+    
+    // remove from provide's list
+    LinkedList1_Remove(&o->provide->depends_list, &o->provide_depends_list_node);
+    
+    // if provide is dying and is empty, let it die
+    if (o->provide->dying && LinkedList1_IsEmpty(&o->provide->depends_list)) {
+        provide_free(o->provide);
+    }
+    
+    // set no provide
+    o->provide = NULL;
+    
+    // update
+    depend_update(o);
+}
+
+static int depend_func_getobj (void *vo, NCD_string_id_t objname, NCDObject *out_object)
+{
+    struct depend *o = vo;
+    
+    if (!o->provide) {
+        return 0;
+    }
+    
+    return NCDModuleInst_Backend_GetObj(o->provide->i, objname, out_object);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "depend_scope",
+        .func_new2 = scope_func_new,
+        .func_die = scope_func_die,
+        .alloc_size = sizeof(struct scope_instance)
+    }, {
+        .type = "depend_scope::provide",
+        .func_new2 = provide_func_new,
+        .func_die = provide_func_die,
+        .alloc_size = sizeof(struct provide)
+    }, {
+        .type = "depend_scope::depend",
+        .func_new2 = depend_func_new,
+        .func_die = depend_func_die,
+        .func_clean = depend_func_clean,
+        .func_getobj = depend_func_getobj,
+        .flags = NCDMODULE_FLAG_CAN_RESOLVE_WHEN_DOWN,
+        .alloc_size = sizeof(struct depend)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_depend_scope = {
+    .modules = modules
+};

+ 2 - 0
ncd/modules/modules.h

@@ -77,6 +77,7 @@ extern const struct NCDModuleGroup ncdmodule_net_ipv6_addr_in_network;
 extern const struct NCDModuleGroup ncdmodule_timer;
 extern const struct NCDModuleGroup ncdmodule_file_open;
 extern const struct NCDModuleGroup ncdmodule_backtrack;
+extern const struct NCDModuleGroup ncdmodule_depend_scope;
 #ifndef BADVPN_EMSCRIPTEN
 extern const struct NCDModuleGroup ncdmodule_regex_match;
 extern const struct NCDModuleGroup ncdmodule_run;
@@ -158,6 +159,7 @@ static const struct NCDModuleGroup *ncd_modules[] = {
     &ncdmodule_timer,
     &ncdmodule_file_open,
     &ncdmodule_backtrack,
+    &ncdmodule_depend_scope,
 #ifndef BADVPN_EMSCRIPTEN
     &ncdmodule_regex_match,
     &ncdmodule_run,