Repository: pantsbuild/pants

Issue:
Create `Tasks` struct before constructing the `Scheduler`
Because the `Tasks` struct is currently created post `scheduler_create`, it's necessary to mutate the content of the `Arc` that is holding it. That opens up the possibility of a panic, and makes initialization of the `RuleGraph` awkward (as demonstrated on #4371).

Instead, the `Tasks` struct should be constructed independently before the `Scheduler`, and then passed in, so that the `Scheduler` can hold an immutable reference without the `Arc`-mutation loophole. 


Discussion:

Resolving patch:
diff --git a/src/python/pants/engine/scheduler.py b/src/python/pants/engine/scheduler.py
--- a/src/python/pants/engine/scheduler.py
+++ b/src/python/pants/engine/scheduler.py
@@ -49,7 +49,14 @@ def __init__(self, native, build_root, ignore_patterns, rule_index, root_subject
     has_products_constraint = SubclassesOf(HasProducts)
 
     # Create the ExternContext, and the native Scheduler.
+    self._tasks = native.new_tasks()
+    self._register_tasks(rule_index.tasks)
+    self._register_intrinsics(rule_index.intrinsics)
+    self._register_singletons(rule_index.singletons)
+    self.root_subject_types = root_subject_types
+
     self._scheduler = native.new_scheduler(
+        self._tasks,
         build_root,
         ignore_patterns,
         Snapshot,
@@ -71,10 +78,6 @@ def __init__(self, native, build_root, ignore_patterns, rule_index, root_subject
         constraint_for(File),
         constraint_for(Link),
       )
-    self._register_tasks(rule_index.tasks)
-    self._register_intrinsics(rule_index.intrinsics)
-    self._register_singletons(rule_index.singletons)
-    self.root_subject_types = root_subject_types
 
   def graph_trace(self):
     with temporary_file_path() as path:
@@ -86,7 +89,7 @@ def graph_trace(self):
   def assert_ruleset_valid(self):
     root_type_ids = self._root_type_ids()
 
-    raw_value = self._native.lib.validator_run(self._scheduler, root_type_ids, len(root_type_ids))
+    raw_value = self._native.lib.validator_run(self._tasks, root_type_ids, len(root_type_ids))
     value = self._from_value(raw_value)
 
     if isinstance(value, Exception):
@@ -129,9 +132,9 @@ def _register_singletons(self, singletons):
     intrinsics, singleton tasks create Runnables that are not cacheable.
     """
     for product_type, rule in singletons.items():
-      self._native.lib.singleton_task_add(self._scheduler,
-                                          Function(self._to_id(rule.func)),
-                                          self._to_constraint(product_type))
+      self._native.lib.tasks_singleton_add(self._tasks,
+                                           Function(self._to_id(rule.func)),
+                                           self._to_constraint(product_type))
 
   def _register_intrinsics(self, intrinsics):
     """Register the given intrinsics dict.
@@ -140,11 +143,11 @@ def _register_intrinsics(self, intrinsics):
     pair. By default, intrinsic tasks create Runnables that are not cacheable.
     """
     for (subject_type, product_type), rule in intrinsics.items():
-      self._native.lib.intrinsic_task_add(self._scheduler,
-                                          Function(self._to_id(rule.func)),
-                                          TypeId(self._to_id(subject_type)),
-                                          self._to_constraint(subject_type),
-                                          self._to_constraint(product_type))
+      self._native.lib.tasks_intrinsic_add(self._tasks,
+                                           Function(self._to_id(rule.func)),
+                                           TypeId(self._to_id(subject_type)),
+                                           self._to_constraint(subject_type),
+                                           self._to_constraint(product_type))
 
   def _register_tasks(self, tasks):
     """Register the given tasks dict with the native scheduler."""
@@ -161,47 +164,46 @@ def _register_tasks(self, tasks):
         registered.add(key)
 
         _, input_selects, func = rule.as_triple()
-        self._native.lib.task_add(self._scheduler, Function(self._to_id(func)), output_constraint)
+        self._native.lib.tasks_task_begin(self._tasks, Function(self._to_id(func)), output_constraint)
         for selector in input_selects:
           selector_type = type(selector)
           product_constraint = self._to_constraint(selector.product)
           if selector_type is Select:
-            self._native.lib.task_add_select(self._scheduler,
-                                             product_constraint)
+            self._native.lib.tasks_add_select(self._tasks, product_constraint)
           elif selector_type is SelectVariant:
             key_buf = self._to_utf8_buf(selector.variant_key)
-            self._native.lib.task_add_select_variant(self._scheduler,
-                                                     product_constraint,
-                                                     key_buf)
+            self._native.lib.tasks_add_select_variant(self._tasks,
+                                                      product_constraint,
+                                                      key_buf)
           elif selector_type is SelectLiteral:
             # NB: Intentionally ignores subject parameter to provide a literal subject.
-            self._native.lib.task_add_select_literal(self._scheduler,
-                                                     self._to_key(selector.subject),
-                                                     product_constraint)
+            self._native.lib.tasks_add_select_literal(self._tasks,
+                                                      self._to_key(selector.subject),
+                                                      product_constraint)
           elif selector_type is SelectDependencies:
-            self._native.lib.task_add_select_dependencies(self._scheduler,
-                                                          product_constraint,
-                                                          self._to_constraint(selector.dep_product),
-                                                          self._to_utf8_buf(selector.field),
-                                                          self._to_ids_buf(selector.field_types))
+            self._native.lib.tasks_add_select_dependencies(self._tasks,
+                                                           product_constraint,
+                                                           self._to_constraint(selector.dep_product),
+                                                           self._to_utf8_buf(selector.field),
+                                                           self._to_ids_buf(selector.field_types))
           elif selector_type is SelectTransitive:
-            self._native.lib.task_add_select_transitive(self._scheduler,
-                                                        product_constraint,
-                                                        self._to_constraint(selector.dep_product),
-                                                        self._to_utf8_buf(selector.field),
-                                                        self._to_ids_buf(selector.field_types))
+            self._native.lib.tasks_add_select_transitive(self._tasks,
+                                                         product_constraint,
+                                                         self._to_constraint(selector.dep_product),
+                                                         self._to_utf8_buf(selector.field),
+                                                         self._to_ids_buf(selector.field_types))
           elif selector_type is SelectProjection:
             if len(selector.fields) != 1:
               raise ValueError("TODO: remove support for projecting multiple fields at once.")
             field = selector.fields[0]
-            self._native.lib.task_add_select_projection(self._scheduler,
-                                                        self._to_constraint(selector.product),
-                                                        TypeId(self._to_id(selector.projected_subject)),
-                                                        self._to_utf8_buf(field),
-                                                        self._to_constraint(selector.input_product))
+            self._native.lib.tasks_add_select_projection(self._tasks,
+                                                         self._to_constraint(selector.product),
+                                                         TypeId(self._to_id(selector.projected_subject)),
+                                                         self._to_utf8_buf(field),
+                                                         self._to_constraint(selector.input_product))
           else:
             raise ValueError('Unrecognized Selector type: {}'.format(selector))
-        self._native.lib.task_end(self._scheduler)
+        self._native.lib.tasks_task_end(self._tasks)
 
   def visualize_graph_to_file(self, filename):
     self._native.lib.graph_visualize(self._scheduler, bytes(filename))
diff --git a/src/python/pants/engine/subsystem/native.py b/src/python/pants/engine/subsystem/native.py
--- a/src/python/pants/engine/subsystem/native.py
+++ b/src/python/pants/engine/subsystem/native.py
@@ -105,7 +105,8 @@
 typedef Value            (*extern_ptr_create_exception)(ExternContext*, uint8_t*, uint64_t);
 typedef RunnableComplete (*extern_ptr_invoke_runnable)(ExternContext*, Value*, Value*, uint64_t, _Bool);
 
-typedef void RawScheduler;
+typedef void Tasks;
+typedef void Scheduler;
 
 typedef struct {
   uint64_t runnable_count;
@@ -147,63 +148,65 @@
                  extern_ptr_invoke_runnable,
                  TypeId);
 
-RawScheduler* scheduler_create(Function,
-                               Function,
-                               Function,
-                               Function,
-                               Function,
-                               Function,
-                               Function,
-                               Function,
-                               TypeConstraint,
-                               TypeConstraint,
-                               TypeConstraint,
-                               TypeConstraint,
-                               TypeConstraint,
-                               TypeConstraint,
-                               TypeConstraint,
-                               TypeConstraint,
-                               TypeConstraint,
-                               TypeConstraint,
-                               TypeId,
-                               TypeId,
-                               Buffer,
-                               BufferBuffer);
-void scheduler_post_fork(RawScheduler*);
-void scheduler_destroy(RawScheduler*);
-
-void intrinsic_task_add(RawScheduler*, Function, TypeId, TypeConstraint, TypeConstraint);
-void singleton_task_add(RawScheduler*, Function, TypeConstraint);
-
-void task_add(RawScheduler*, Function, TypeConstraint);
-void task_add_select(RawScheduler*, TypeConstraint);
-void task_add_select_variant(RawScheduler*, TypeConstraint, Buffer);
-void task_add_select_literal(RawScheduler*, Key, TypeConstraint);
-void task_add_select_dependencies(RawScheduler*, TypeConstraint, TypeConstraint, Buffer, TypeIdBuffer);
-void task_add_select_transitive(RawScheduler*, TypeConstraint, TypeConstraint, Buffer, TypeIdBuffer);
-void task_add_select_projection(RawScheduler*, TypeConstraint, TypeConstraint, Buffer, TypeConstraint);
-void task_end(RawScheduler*);
-
-uint64_t graph_len(RawScheduler*);
-uint64_t graph_invalidate(RawScheduler*, BufferBuffer);
-void graph_visualize(RawScheduler*, char*);
-void graph_trace(RawScheduler*, char*);
-
-void execution_reset(RawScheduler*);
-void execution_add_root_select(RawScheduler*, Key, TypeConstraint);
-void execution_add_root_select_dependencies(RawScheduler*,
+Tasks* tasks_create();
+void tasks_task_begin(Tasks*, Function, TypeConstraint);
+void tasks_add_select(Tasks*, TypeConstraint);
+void tasks_add_select_variant(Tasks*, TypeConstraint, Buffer);
+void tasks_add_select_literal(Tasks*, Key, TypeConstraint);
+void tasks_add_select_dependencies(Tasks*, TypeConstraint, TypeConstraint, Buffer, TypeIdBuffer);
+void tasks_add_select_transitive(Tasks*, TypeConstraint, TypeConstraint, Buffer, TypeIdBuffer);
+void tasks_add_select_projection(Tasks*, TypeConstraint, TypeConstraint, Buffer, TypeConstraint);
+void tasks_task_end(Tasks*);
+void tasks_intrinsic_add(Tasks*, Function, TypeId, TypeConstraint, TypeConstraint);
+void tasks_singleton_add(Tasks*, Function, TypeConstraint);
+void tasks_destroy(Tasks*);
+
+Scheduler* scheduler_create(Tasks*,
+                            Function,
+                            Function,
+                            Function,
+                            Function,
+                            Function,
+                            Function,
+                            Function,
+                            Function,
+                            TypeConstraint,
+                            TypeConstraint,
+                            TypeConstraint,
+                            TypeConstraint,
+                            TypeConstraint,
+                            TypeConstraint,
+                            TypeConstraint,
+                            TypeConstraint,
+                            TypeConstraint,
+                            TypeConstraint,
+                            TypeId,
+                            TypeId,
+                            Buffer,
+                            BufferBuffer);
+void scheduler_post_fork(Scheduler*);
+void scheduler_destroy(Scheduler*);
+
+uint64_t graph_len(Scheduler*);
+uint64_t graph_invalidate(Scheduler*, BufferBuffer);
+void graph_visualize(Scheduler*, char*);
+void graph_trace(Scheduler*, char*);
+
+void execution_reset(Scheduler*);
+void execution_add_root_select(Scheduler*, Key, TypeConstraint);
+void execution_add_root_select_dependencies(Scheduler*,
                                             Key,
                                             TypeConstraint,
                                             TypeConstraint,
                                             Buffer,
                                             TypeIdBuffer);
-ExecutionStat execution_execute(RawScheduler*);
-RawNodes* execution_roots(RawScheduler*);
+ExecutionStat execution_execute(Scheduler*);
+RawNodes* execution_roots(Scheduler*);
 
-Value validator_run(RawScheduler*, TypeId*, uint64_t);
+Value validator_run(Tasks*, TypeId*, uint64_t);
 
-void rule_graph_visualize(RawScheduler*, TypeId*, uint64_t, char*);
-void rule_subgraph_visualize(RawScheduler*, TypeId, TypeConstraint, char*);
+void rule_graph_visualize(Scheduler*, TypeId*, uint64_t, char*);
+void rule_subgraph_visualize(Scheduler*, TypeId, TypeConstraint, char*);
 
 void nodes_destroy(RawNodes*);
 '''
@@ -673,7 +676,11 @@ def unpack(self, cdata_ptr, count):
   def buffer(self, cdata):
     return self.ffi.buffer(cdata)
 
+  def new_tasks(self):
+    return self.gc(self.lib.tasks_create(), self.lib.tasks_destroy)
+
   def new_scheduler(self,
+                    tasks,
                     build_root,
                     ignore_patterns,
                     construct_snapshot,
@@ -700,6 +707,7 @@ def tc(constraint):
       return TypeConstraint(self.context.to_id(constraint))
 
     scheduler = self.lib.scheduler_create(
+        tasks,
         # Constructors/functions.
         Function(self.context.to_id(construct_snapshot)),
         Function(self.context.to_id(construct_snapshots)),


Test patch: