Commits

Pierre Carbonnelle committed 9f4ecfe

add Logic module to support multi-model applications in a thread safe way

Comments (0)

Files changed (3)

pyDatalog/Logic.py

+"""
+pyDatalog
+
+Copyright (C) 2013 Pierre Carbonnelle
+
+This library is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as
+published by the Free Software Foundation; either version 2 of the
+License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc.  51 Franklin St, Fifth Floor, Boston, MA 02110-1301
+USA
+
+"""
+
+import copy
+import threading
+import weakref
+
+try:
+    from . import pyEngine
+except ValueError:
+    import pyEngine
+
+class Logic(object):
+    """ 
+    per-thread singleton class containing the pyEngine Logic in the current thread.
+    Logic() returns the pyEngine logic in the current thread, so that it can be passed to another thread.
+    Logic(logic) initializes the logic in the current thread with logic, and returns it.
+    """
+    tl = threading.local() # contains the Logic in the current thread
+    def __new__(cls, logic=None):
+        if isinstance(logic, cls):
+            Logic.tl.logic = copy.copy(logic) 
+        elif not hasattr(Logic.tl, 'logic'):
+            Logic.tl.logic = object.__new__(cls)
+        return Logic.tl.logic
+    
+    def __init__(self, logic=None):
+        if not (logic) and not (hasattr(self, 'Db')):
+            pyEngine.clear() # make sure the singleton has what's needed
+            
+    def clear(self):
+        """ move the logic to the current thread and clears it """
+        Logic(self) # just to be sure
+        pyEngine.clear()
+                
+pyEngine.Logic = Logic # share Logic with pyEngine

pyDatalog/__init__.py

+
+try:
+    from . import version
+    from . import Logic
+except ValueError:
+    import version
+    import Logic
+    
+Logic = Logic.Logic # give easy access to the Logic class
+
+Logic().clear() # initialize the logic in the current thread
+    

pyDatalog/pyEngine.py

 Auto_print = False # True => automatically prints the result of a query
 
 Python_resolvers = {} # dictionary  of python functions that can resolve a predicate
-
-Thread_storage = threading.local()
+Logic = None # place holder for Logic class from Logic module
 
 #       DATA TYPES          #####################################
 
         assert isinstance(pred_name, six.string_types)
         _id = '%s/%i' % (pred_name, arity)
         with Pred.lock:
-            o = Thread_storage.Pred_registry.get(_id, Interned.notFound)
+            o = Logic.tl.logic.Pred_registry.get(_id, Interned.notFound)
             if o is Interned.notFound: 
                 o = object.__new__(cls) # o is the ref that keeps it alive
                 o.id = _id
                 o.prim = None
                 o.expression = None
                 o.aggregate = aggregate
-                Thread_storage.Pred_registry[_id] = o
+                Logic.tl.logic.Pred_registry[_id] = o
         if aggregate: o.aggregate = aggregate
         return o
     
     
     def _renamed(self, new_name):
         _id = '%s/%i' % (new_name, len(self.terms))
-        pred= Thread_storage.Pred_registry.get(_id, new_name)
+        pred= Logic.tl.logic.Pred_registry.get(_id, new_name)
         return Literal(pred, list(self.terms), prearity=self.pred.prearity)
         
     def rebased(self, parent_class): 
 # The database stores predicates that contain clauses.  
 
 def insert(pred):
-    Thread_storage.Db[pred.id] = pred
+    Logic.tl.logic.Db[pred.id] = pred
     return pred
 
 def remove(pred):
-    if pred.id in Thread_storage.Db : del Thread_storage.Db[pred.id]
+    if pred.id in Logic.tl.logic.Db : del Logic.tl.logic.Db[pred.id]
     return pred
     
 
 
 def find(literal):
     tag = get_tag(literal)
-    return Thread_storage.Subgoals.get(tag)
+    return Logic.tl.logic.Subgoals.get(tag)
 
 def merge(subgoal):
-    Thread_storage.Subgoals[get_tag(subgoal.literal)] = subgoal
+    Logic.tl.logic.Subgoals[get_tag(subgoal.literal)] = subgoal
 
 
 class Subgoal(object):
         self.thunk()
         
 def schedule(task):
-    return Thread_storage.Tasks.append(task)
+    return Logic.tl.logic.Tasks.append(task)
 
 def complete(subgoal, post_thunk):
     """makes sure that thunk() is completed before calling post_thunk and resuming processing of other thunks"""
-    Ts = Thread_storage
+    Ts = Logic.tl.logic
     Ts.Stack.append((Ts.Subgoals, Ts.Tasks, Ts.Goal)) # save the environment to the stack. Invoke will eventually do the Stack.pop().
     Ts.Subgoals, Ts.Tasks, Ts.Goal = {}, deque(), subgoal
     thunk = lambda subgoal=subgoal: merge(subgoal) or search(subgoal)
 
 def invoke(subgoal):
     """ Invoke the tasks. Each task may append new tasks on the schedule."""
-    Ts = Thread_storage
+    Ts = Logic.tl.logic
     thunk = lambda subgoal=subgoal: search(subgoal)
     Ts.Tasks, Ts.Subgoals, Ts.Goal = deque([Thunk(thunk),]), {}, subgoal
     while (Ts.Tasks or Ts.Stack) and not Ts.Goal.is_done:
     
 def add_clause(subgoal, clause):
     """ SLG_NEWCLAUSE in the reference article """
-    if subgoal.is_done or Thread_storage.Goal.is_done:
+    if subgoal.is_done or Logic.tl.logic.Goal.is_done:
         return # no need to keep looking if THE answer is found already
     if not clause.body:
         return fact(subgoal, clause.head)
                     lambda base_subgoal=base_subgoal, subgoal=subgoal, literal=literal:
                         _aggregate(base_subgoal, subgoal, literal))
             return
-        elif literal.pred.id in Thread_storage.Db: # has a datalog definition, e.g. p(X), p[X]==Y
+        elif literal.pred.id in Logic.tl.logic.Db: # has a datalog definition, e.g. p(X), p[X]==Y
             for clause in relevant_clauses(literal):
                 renamed = rename_clause(clause)
                 env = unify(literal, renamed.head)
     return l._in(r) if op=='_pyD_in' else l._not_in(r) if op=='_pyD_not_in' else compare(l,op,r)
 
 def clear():
-    Thread_storage.Db = {}
-    Thread_storage.Pred_registry = weakref.WeakValueDictionary()
-    Thread_storage.Subgoals = {}
-    Thread_storage.Tasks = None
-    Thread_storage.Stack = []
-    Thread_storage.Goal = None       
+    """ clears the logic """
+    Logic.tl.logic.Db = {}
+    Logic.tl.logic.Pred_registry = weakref.WeakValueDictionary()
+    Logic.tl.logic.Subgoals = {}
+    Logic.tl.logic.Tasks = None
+    Logic.tl.logic.Stack = []
+    Logic.tl.logic.Goal = None       
 
     insert(Pred("==", 2)).prim = equals_primitive
     add_iter_prim_to_predicate(insert(Pred("<" , 2)), compare_primitive)
     add_iter_prim_to_predicate(insert(Pred(">" , 2)), compare_primitive)
     add_iter_prim_to_predicate(insert(Pred("_pyD_in", 2)), compare_primitive)
     add_iter_prim_to_predicate(insert(Pred("_pyD_not_in", 2)), compare_primitive)
-
-clear()