SWI-Prolog / packages-jpl

JPL: The Prolog <-> Java interface
BSD 2-Clause "Simplified" License
52 stars 32 forks source link

Reentrant caching? #56

Open ghost opened 4 years ago

ghost commented 4 years ago

Is JPL supposed to be multi-threaded capabel, i.e. reentrant? Because this code doesn’t work as a cache: There might be race conditions where two or more jpl_iref_type_cache/2 are asserted. A reentrant cache is a little bit more difficult to implement and needs a mutex.

jpl_object_to_type(Ref, Type) :-
    jpl_is_object(Ref),
    (   jpl_iref_type_cache(Ref, T)
    ->  true                                % T is Tag's type
    ;   jpl_object_to_class(Ref, Cobj),     % else get ref to class obj
        jpl_class_to_type(Cobj, T),         % get type of class it denotes
        jpl_assert(jpl_iref_type_cache(Ref,T))
    ),
    Type = T.

One way to do it, is as follows. But it has the drawback that time is spent inside the mutx by ‘jpl_object_to_class/2’ etc… Using destructive operations which would be also seen through facts, this can be even done outside of the mutex, and would be faster. So this is only an imperfect sketch:

jpl_object_to_type(Ref, Type) :-
    jpl_is_object(Ref),
    (   jpl_iref_type_cache(Ref, T)
    ->  true                                % T is Tag's type
    ;   with_mutex(jpl_iref_type_cache_mutex, 
               (jpl_iref_type_cache(Ref, T)        % need to do check again
               ->  true;
               jpl_object_to_class(Ref, Cobj),     % else get ref to class obj
               jpl_class_to_type(Cobj, T),         % get type of class it denotes
               jpl_assert(jpl_iref_type_cache(Ref,T)))
        )
    ),
    Type = T.

Maybe alternatively an elegant approach could be using SWI-Prolog shared tabling.

anionic commented 4 years ago

JPL now aims to support multi threads (with much help from Jan), although it didn't originally, and I didn't review the (various) caching :-/

Couldn't we mutex just the jpl_assert? or am I missing something.

Also, any caching was speculative, and not demonstrably compute-saving...

ghost commented 4 years ago

Yeah, I guess this would be also a form of caching:


jpl_object_to_type(Ref, Type) :-
    jpl_is_object(Ref),
    (   jpl_iref_type_cache(Ref, T)
    ->  true                                % T is Tag's type
    ;   jpl_object_to_class(Ref, Cobj),     % else get ref to class obj
        jpl_class_to_type(Cobj, T),         % get type of class it denotes
        with_mutex(jpl_iref_type_cache_mutex, 
                  (retractall(jpl_iref_type_cache(Ref,_)),
                  assertz(jpl_iref_type_cache(Ref,T)))
        )
    ),
    Type = T.

In the above a race condition might appear that two or more threads calculate T, but in the end there will be only one entry. I am using retractall/1 to remove a previous entry. You

could also use a if-then-else check instead, i.e. a conditional asssertz/1. As long as there is a mutex, the thread will have the critical section exclusively, and can determine whether it

needs to add a cache entry or not. Problem is how to box it into a meta call jpl_assert/1 or better jpl_update/1. You need to know what are the primary key arguments and what are the

data arguments of the item you want to update.

JanWielemaker commented 4 years ago

This issue has been mentioned on SWI-Prolog. There might be relevant details there:

https://swi-prolog.discourse.group/t/global-variables-and-threads/2519/5