lucy-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From "Andrew S. Townley" <...@atownley.org>
Subject Re: [lucy-dev] Host overriding of all non-final methods
Date Thu, 03 Mar 2011 13:47:58 GMT

On 3 Mar 2011, at 1:35 PM, Marvin Humphrey wrote:

> On Thu, Mar 03, 2011 at 09:03:39AM +0000, Andrew S. Townley wrote:
>> Lazy or not, I think it's better to fail silently now and then try and build
>> better checks into the clownfish compiler or some kind of clownlint tool
>> later than imposing exception handling overhead on potentially every method
>> call (unless I'm misunderstanding the impact of option B).
> 
> The host language's OO hierarchy is only walked once per subclass.  Once Lucy
> creates a VTable singleton and stores it away, that VTable will not be
> modified.  Thus, there is no extra overhead imposed on every method call
> within the Lucy core -- but we don't support all the features of dynamic
> languages.
> 
> Generally speaking, the VTable singleton for a host-defined subclass is
> created the first time a constructor for that subclass is called[1].  It is at
> this moment that we would attempt to detect illegal overriding of "final"
> methods.  Here's the code from from VTable_singleton() in
> core/Lucy/Object/VTable.c which enables host method overrides, modified to
> enforce "final" methods:
> 
>     // Allow host methods to override. 
>     novel_host_methods = VTable_novel_host_methods(subclass_name);
>     num_novel = VA_Get_Size(novel_host_methods);
>     if (num_novel) {
>         Hash *meths = Hash_new(num_novel);
>         uint32_t i;
>         CharBuf *scrunched = CB_new(0);
>         ZombieCharBuf *callback_name = ZCB_BLANK();
>         for (i = 0; i < num_novel; i++) {
>             CharBuf *meth = (CharBuf*)VA_fetch(novel_host_methods, i);
>             S_scrunch_charbuf(meth, scrunched);
>             Hash_Store(meths, (Obj*)scrunched, INCREF(&EMPTY));
>         }
>         cfish_Callback **callbacks
>             = (cfish_Callback**)singleton->callbacks;
>         for (i = 0; callbacks[i] != NULL; i++) {
>             cfish_Callback *const callback = callbacks[i];
>             ZCB_Assign_Str(callback_name, callback->name,
>                 callback->name_len);
>             S_scrunch_charbuf((CharBuf*)callback_name, scrunched);
>             if (Hash_Fetch(meths, (Obj*)scrunched)) {
> +                if (callback->method_is_final) {
> +                    THROW(ERR, 
> +                        "Can't override final method '%o' in class '%o'",
> +                        meth, class_name);
> +                }
>                 VTable_Override(singleton, callback->func, 
>                     callback->offset);
>             }
>         }
>         DECREF(scrunched);
>         DECREF(meths);
>     }
>     DECREF(novel_host_methods);
> 
> Once a VTable is created, if you subsequently attempt to modify behavior using
> dynamic features such as Ruby's singleton methods, you will get inconsistent
> results.  The behavior will be visible from the host language, but it might
> not be visible from inside the Lucy core.  You only get one shot to tell Lucy
> that it needs to call back into the host.  If a method was not overridden at
> the moment the VTable was created, Lucy won't notice if it gets overridden
> later.
> 
>    class MyQueryParser < Lucy::Search::QueryParser
>    end
> 
>    query_parser = MyQueryParser.new(schema)
>    def query_parser.expand_leaf
>        puts "This code will not be called from within the Lucy core."
>    end
> 
> Clownfish's design is a compromise which lets us support a simple class-based
> single-inheritance OO model without sacrificing speed.  Method dispatch in
> Clownfish uses a vtable-based double dereference mechanism[2] -- typical for
> C++ or Java.  It's less flexible than the dispatch techniques used by dynamic
> languages like Ruby, Python and Perl, but until the host callback mechanism
> gets invoked, it's much, much faster.
> 
> Marvin Humphrey
> 
> [1] All parent VTables must be created first, so if the VTable for a host
>    subclass two generations removed from its Lucy ancestor is needed before
>    its parent, the parent VTable will be initialized first as a side effect.
> 
> [2] http://en.wikipedia.org/wiki/Virtual_method_table


That all makes sense, but I guess I was coming from the perspective of you never know when
those objects are going to be instantiated.  I was trying to have the least possible unexpected
disruptions (e.g. fail safe) vs. potentially random uncaught exceptions in potentially complex
systems that may or may not use plugins to configure things like search behavior.

Couldn't throwing the exception this way make it possible for bad code loaded dynamically
at runtime to bring down an entire system?  If you never know when this exception can potentially
be thrown, you'll never be able to reliably catch it (except perhaps in a top-level run loop)
and avoid potential chaos.

Barring this, I think Nathan's suggestion of some kind of runtime behavior configuration via
environment variables or initialization parameters would be preferable to "random" exceptions
because of programmer error.  However, maybe I'm being a bit paranoid as well... ;)
--
Andrew S. Townley <ast@atownley.org>
http://atownley.org


Mime
View raw message