Qore has had a big design problem in its memory management regarding object collection; basically it was possible to make circular references to objects, and those objects would not be destroyed automatically, resulting in a memory leak.
The reason for this is that Qore used a simple strong reference count for object scope, so if object A pointed to object B, object B pointed to object A, then a circular reference would exist and neither object would be destroyed (or collected) when the objects would otherwise go out of scope. Note that Qore treats objects like Java does in that objects are always passed as a copy of a reference to the object instead of by value. Weak references to objects are also supported, but weak object references do not affect object collection (ie when the destructor is run on the object); normally the object's destructor is run on the object only when the strong reference count reaches zero (or the object is manually deleted with the delete operator).
Also due to Qore's multi-threaded nature, objects can go out of scope at any time since they can be deleted in another thread at any time. Qore objects are thread-safe and references to an objects are wrapped in a read-write lock - as are all shared lvalues in Qore - basically access to all lvalues in Qore are wrapped in read-write locks except accesses to "pure" local variables, which are local variables where references are not taken of them and also not used in a closure, either of which case makes it possible to use the local variable in another thread and therefore causes the local variable to be created specially so that accesses are wrapped in read-write locks.
I considered using something like Java's garbage collection approach with a dedicated thread that would scan and collect objects, but I always wanted to do a deterministic garbage collector so that Qore's resource management approach with objects could be applied deterministically. I finally have an initial working implementation of a deterministic garbage collection algorithm for Qore that is thread-safe, does not require a dedicated collector thread, has a solid deadlock-avoidance mechanism, and exhibits acceptable performance (which I expect can be further improved).
With this new approach, objects are collected immediately when they only have strong references that belong to a recursive cycle; so resources managed by the object are released in a deterministic way even when the objects participate in a recursive reference cycle.
I would like to describe the algorithm here including the locking approach and the deadlock-avoidance mechanism.
Basically the idea is to determine the number of strong references to an object that belong to a circular reference. In fact, it is more complicated than this, because you have to consider the entire recursive directed graph as a whole and not a single object. The recursive directed graph in this case is the set of all objects participating in the recursive reference chain. If any object is reachable in the chain and also contains link(s) (ie members) to other objects in the chain, then it is a member of the recursive directed graph. So the idea is to maintain the recursive strong reference count of every object in the recursive directed graph and then, when any member of the graph is strongly dereferenced, then the entire graph is checked to see if it can be collected, meaning that each member of the graph is checked to see if the strong reference count is equal to its recursive reference count. If any single member of the graph has non-recursive references, then no object in the graph can be collected; only when the strong reference count equals the recursive reference count for all objects in the graph can the objects in the graph be collected.
This is accomplished by performing a scan of all reachable objects whenever a potentially-relevant change is made to an object. In this case the recursive reference counts are calculated for all objects in the graph.
To accomplish this, each object is locked specially. In fact objects now have a special form of read-write lock that includes a special form of the read lock called an rsection lock. The rsection lock in a Qore object is a read lock that is unique in that first the read lock is acquired and then only one thread can hold the rsection lock at a time. This allows objects to be scanned for recursive graphs while also allowing them to be read in other threads to maximize concurrency. Additionally, since multiple rsection locks are acquired when performing recursive scanning (one for each object) and held for the duration of the scan, and since holding multiple locks could lead to deadlocks, and since this can and is performed in multiple threads simultaneously in multi-threaded object-oriented Qore programs, and since otherwise Qore avoids holding multiple locks simultaneously, the deadlock avoidance approach here is to apply a transaction-handling approach to the rsection scan and if any rsection lock cannot be acquired, the transaction is rolled back and we wait for a confirmation from the other thread that the contentious rsection lock has been released. Also a transaction counter in each object is maintained, and, after waiting for a contentious rsection lock to be released, we see that the transaction count for the root object has been changed, then we know that the rsection scan has already taken place, so we exit the scan immediately.
Basically all changes to objects are stored in temporary data structures and then only committed to the objects in the graph if all rsection locks are acquired.
Also all containers (lists, hashes, and objects) contain a reachable object count, which is a sum of the children (list elements, hash keys, or object members) that have at least one object reachable through them. This turned out to be efficient to calculate. This allows us to ignore any container that has no reachable objects when performing rsection scans.
Additionally if an rsection scan fails due to lock contention after an lvalue change, the scanned objects are marked with an invalid rsection so that a deterministic scan is made on the next strong dereference. When performing rsection scans due to a strong dereference, the scan is repeated after an rsection rollback until it is successful to guarantee deterministic collection when only recursive references remain.
There is still certainly a lot of room for improvement to this algorithm. For example, the rsection transaction could be compared to any existing rsection graph and left in place if identical results are found, or possibly a delta operation could be performed on an existing rsection graph.
While this algorithm is complicated, the goal of achieving deterministic garbage collection is a valid one in my opinion.
Knowing exactly when your objects will be collected even in the case of participation in recursive directed graphs of strong references provides an advantage to Qore programmers regarding resource management with objects.
Currently deterministic garbage collection is enabled by default in Qore trunk, and I plan on continuing to work on it.
Feedback on this subject would be appreciated.
Translate
Tuesday, September 30, 2014
Thursday, March 7, 2013
REST vs RPC in Web Service Development
Ive been implementing web services using lightweight web service protocols such as XML-RPC, JSON-RPC and (my favorite although proprietary) YAML-RPC for some time now.
I had some discussions last year about web service development using REST, a concept I had heard about for some time, but I did not see the advantages of it. I knew that it had become the predominate web development architecture model, but for some reason I could not see the advantages of REST over pure lightweight RPC approaches.
My first experiences with an RPC protocol over HTTP was with SOAP, which is a technology I've grown to strongly dislike; now I consider SOAP a horrible enterprise technology since it's cumbersome and expensive to implement and often suffers from serious compatibility issues between different vendor implementations (Qore also has SOAP support - both server and client, however I would recommend avoiding it if you can). My great appreciation for the lightweight RPC over HTTP protocols listed above is to no small degree based on my dislike of SOAP, so when hearing and reading about REST, I could not see that it would be as great a leap as from SOAP to XML-RPC for example.
(As a footnote Wikipedia lists XML-RPC as the precursor to SOAP, which makes SOAP an absolutely classic case of overengineering by committee - since from my point of view XML-RPC with all its faults is vastly superior to SOAP. Also here is a link which humorously sums up my thoughts on SOAP: http://harmful.cat-v.org/software/xml/soap/simple)
However, finally I've gotten into REST a little deeper, and having developed an initial RestHandler for the Qore HTTP server, the advantages of a REST architecture are completely clear and compelling.
Using REST forces you to have a logical and straightforward organization of your web service development. Each type of object will have it's own URL, and (in the Qore RestHandler at least) it will have a dedicated class to handle requests to objects of that type.
For example, in the Qore RestHandler, if you register a class with the name "workflows" at the root level of your REST handler, then a request like
GET /workflows
will be directed to your workflow class's get() method, which will handle the request.
Classes (and therefore URLs) can be stacked, so a request like
GET /workflows/1/instances/2?action=stop
Will be directed to the appropriate class's "getStop()" method. If the appropriate method name (derived from the HTTP method name in the request and any action verb given as a URI argument) is not implemented in the class, then the RestHandler returns a user-friendly 400 response.
This easy and logical hierarchical organization of the objects corresponding to the URLs along with the simple dispatching to handler methods of objects of the appropriate class makes implementing the REST service trivial in Qore.
Additionally, the RestHandler transparently takes care of deserializing request bodies based on the HTTP Content-Type header and serializing any response body based on the client's HTTP Accept header.
This also makes the REST approach superior to the lightweight RPC protocols mentioned above.
It's easy to see why REST is the dominant web service architecture; Qore's RestHandler will continue to be under active development for the upcoming release of Qore (0.8.8), and I hope it will be useful for others as well.
I had some discussions last year about web service development using REST, a concept I had heard about for some time, but I did not see the advantages of it. I knew that it had become the predominate web development architecture model, but for some reason I could not see the advantages of REST over pure lightweight RPC approaches.
My first experiences with an RPC protocol over HTTP was with SOAP, which is a technology I've grown to strongly dislike; now I consider SOAP a horrible enterprise technology since it's cumbersome and expensive to implement and often suffers from serious compatibility issues between different vendor implementations (Qore also has SOAP support - both server and client, however I would recommend avoiding it if you can). My great appreciation for the lightweight RPC over HTTP protocols listed above is to no small degree based on my dislike of SOAP, so when hearing and reading about REST, I could not see that it would be as great a leap as from SOAP to XML-RPC for example.
(As a footnote Wikipedia lists XML-RPC as the precursor to SOAP, which makes SOAP an absolutely classic case of overengineering by committee - since from my point of view XML-RPC with all its faults is vastly superior to SOAP. Also here is a link which humorously sums up my thoughts on SOAP: http://harmful.cat-v.org/software/xml/soap/simple)
However, finally I've gotten into REST a little deeper, and having developed an initial RestHandler for the Qore HTTP server, the advantages of a REST architecture are completely clear and compelling.
Using REST forces you to have a logical and straightforward organization of your web service development. Each type of object will have it's own URL, and (in the Qore RestHandler at least) it will have a dedicated class to handle requests to objects of that type.
For example, in the Qore RestHandler, if you register a class with the name "workflows" at the root level of your REST handler, then a request like
GET /workflows
will be directed to your workflow class's get() method, which will handle the request.
Classes (and therefore URLs) can be stacked, so a request like
GET /workflows/1/instances/2?action=stop
Will be directed to the appropriate class's "getStop()" method. If the appropriate method name (derived from the HTTP method name in the request and any action verb given as a URI argument) is not implemented in the class, then the RestHandler returns a user-friendly 400 response.
This easy and logical hierarchical organization of the objects corresponding to the URLs along with the simple dispatching to handler methods of objects of the appropriate class makes implementing the REST service trivial in Qore.
Additionally, the RestHandler transparently takes care of deserializing request bodies based on the HTTP Content-Type header and serializing any response body based on the client's HTTP Accept header.
This also makes the REST approach superior to the lightweight RPC protocols mentioned above.
It's easy to see why REST is the dominant web service architecture; Qore's RestHandler will continue to be under active development for the upcoming release of Qore (0.8.8), and I hope it will be useful for others as well.
Saturday, March 2, 2013
Class Inheritance vs Class Wrapping
Qore supports "standard" class inheritance for polymorphism, but also supports another construct that may be unique to Qore. In Qore there is the possibility of "wrapping" a class and providing a compatible interface with the wrapped class without using inheritance.
There are several advantages and also some drawbacks to this approach. Let me first explain what I mean by "wrapping" a class and also explain how it's done in Qore.
To "wrap" a class, you embed an object of the desired class as a member of the new class and implement a methodGate() method that redirects method calls from outside the class to unimplemented methods. Additionally a memberGate() method can be implemented to redirect accesses to unknown members from outside the class and return an arbitrary value for the member access.
For example, you can wrap a class to provide logging for all method calls or provide external thread synchronization to an unsynchronized class. The following is an example that does both.
class MySocket {
private {
Socket $.sock();
Mutex $.m();
}
static log(string $fmt) {
vprintf(now_us().format("YYYY-MM-DD HH:mm:SS.us Z") + ": " + $fmt, $argv);
}
any methodGate(string $method_name) {
MySocket::log("MySocket::methodGate() method called: %y args: %y\n", $method_name, $argv);
$.m.lock();
on_exit $.m.unlock();
return callObjectMethodArgs($.sock, $method_name, $argv);
}
}
This can be very useful for testing and for other purposes, requires very little typing to implement, and its purpose is clear from the implementation (even without comments). Additionally, this method is completely independent of the definition of the wrapped class.
The main disadvantage is that it doesn't claim compatibility with the Socket class, so you cannot pass an object of class MySocket to code that expects a Socket object.
Currently there is no way in Qore to wrap a class in this way and to remain type-compatible (i.e. to appear to be a subclass of the wrapped class). To remain type-compatible in this way, every method of the class has to be explicitly wrapped as above, and if the wrapped class has any method interface changes (for example, new method), the subclass has to changed accordingly.
However despite the drawbacks, it's still a very useful construct. Possibly a future version of Qore will allow such wrapped classes to claim class compatibility with the wrapped class and therefore address the typing issues.
There are several advantages and also some drawbacks to this approach. Let me first explain what I mean by "wrapping" a class and also explain how it's done in Qore.
To "wrap" a class, you embed an object of the desired class as a member of the new class and implement a methodGate() method that redirects method calls from outside the class to unimplemented methods. Additionally a memberGate() method can be implemented to redirect accesses to unknown members from outside the class and return an arbitrary value for the member access.
For example, you can wrap a class to provide logging for all method calls or provide external thread synchronization to an unsynchronized class. The following is an example that does both.
class MySocket {
private {
Socket $.sock();
Mutex $.m();
}
static log(string $fmt) {
vprintf(now_us().format("YYYY-MM-DD HH:mm:SS.us Z") + ": " + $fmt, $argv);
}
any methodGate(string $method_name) {
MySocket::log("MySocket::methodGate() method called: %y args: %y\n", $method_name, $argv);
$.m.lock();
on_exit $.m.unlock();
return callObjectMethodArgs($.sock, $method_name, $argv);
}
}
This can be very useful for testing and for other purposes, requires very little typing to implement, and its purpose is clear from the implementation (even without comments). Additionally, this method is completely independent of the definition of the wrapped class.
The main disadvantage is that it doesn't claim compatibility with the Socket class, so you cannot pass an object of class MySocket to code that expects a Socket object.
Currently there is no way in Qore to wrap a class in this way and to remain type-compatible (i.e. to appear to be a subclass of the wrapped class). To remain type-compatible in this way, every method of the class has to be explicitly wrapped as above, and if the wrapped class has any method interface changes (for example, new method), the subclass has to changed accordingly.
However despite the drawbacks, it's still a very useful construct. Possibly a future version of Qore will allow such wrapped classes to claim class compatibility with the wrapped class and therefore address the typing issues.
Sunday, February 3, 2013
Exceptions vs Error Codes
Recently I was reading about throwing exceptions vs returning error codes, and I realized that this is a very interesting topic in programming language design that had been in the back of my head, but deserves to be properly thought through.
I don't believe that one approach is better than the other for all cases; from my point of view the biggest argument for exceptions over return codes is that with exceptions you avoid situations where errors are silently ignored (resulting in confusing results for users and difficult to debug situations for the programmer).
Basically Qore tries to be as intuitive as possible for the programmer (and obviously fails sometimes, as seen from the occasional design and interface changes as the language progresses), and therefore should try to get this right.
After thinking about it a bit, it seems to me that Qore builtin code should always throw exceptions if errors are not likely to occur (and therefore be more likely to be ignored). Examples of this would be Socket send methods (changed from returning error codes to throwing exceptions in 0.8.6) and File write methods (even less likely to throw exceptions, already changed to throw exceptions in svn - to be released in 0.8.7 shortly).
It also seems that for code that is more likely to throw an error could justifiably return error codes instead of throwing exceptions, as ignoring the return code would be clearly bad programming practice. If the code in question already returns a result code, then this model fits even better.
Making a language as intuitive as possible for all programmers is not easy. Basically I can only try to do it for myself and try to think through and make educated guesses about what should work for others. However at the end of the day, it's all about maximizing productivity. Having silent errors in an important program could cost a lot of money to debug. If the programming language is not intuitive, then it also costs money every time programmers get stuck on some weirdness with the language or have to debug problems caused by the same. Intuitive programming languages are also more fun to program in.
This last point is the main reason why Qore's design was originally based on perl - I found perl very intuitive and wanted to make a language like perl that was suitable for enterprise development. I know that quite a few programmers do not like perl, but possibly a lot of those have never programmed much in it. Anyway, I believe Qore is getting better and better with each release, I find it more intuitive, and I hope those that are brave enough to try Qore do too.
I don't believe that one approach is better than the other for all cases; from my point of view the biggest argument for exceptions over return codes is that with exceptions you avoid situations where errors are silently ignored (resulting in confusing results for users and difficult to debug situations for the programmer).
Basically Qore tries to be as intuitive as possible for the programmer (and obviously fails sometimes, as seen from the occasional design and interface changes as the language progresses), and therefore should try to get this right.
After thinking about it a bit, it seems to me that Qore builtin code should always throw exceptions if errors are not likely to occur (and therefore be more likely to be ignored). Examples of this would be Socket send methods (changed from returning error codes to throwing exceptions in 0.8.6) and File write methods (even less likely to throw exceptions, already changed to throw exceptions in svn - to be released in 0.8.7 shortly).
It also seems that for code that is more likely to throw an error could justifiably return error codes instead of throwing exceptions, as ignoring the return code would be clearly bad programming practice. If the code in question already returns a result code, then this model fits even better.
Making a language as intuitive as possible for all programmers is not easy. Basically I can only try to do it for myself and try to think through and make educated guesses about what should work for others. However at the end of the day, it's all about maximizing productivity. Having silent errors in an important program could cost a lot of money to debug. If the programming language is not intuitive, then it also costs money every time programmers get stuck on some weirdness with the language or have to debug problems caused by the same. Intuitive programming languages are also more fun to program in.
This last point is the main reason why Qore's design was originally based on perl - I found perl very intuitive and wanted to make a language like perl that was suitable for enterprise development. I know that quite a few programmers do not like perl, but possibly a lot of those have never programmed much in it. Anyway, I believe Qore is getting better and better with each release, I find it more intuitive, and I hope those that are brave enough to try Qore do too.
Friday, August 24, 2012
Solving the Java Classloader Class Compatibility Problem in Qore
In Java, if you load the same class from two different classloaders, the JVM will see those as two different classes, and not even casting will work to force the JVM to see them as compatible classes.
Qore has a similar problem; Qore uses the Program class to encapsulate code; multiple Program objects can exist in a program (or script), and classes created from the same source code but from different Program objects were (before Qore 0.8.4) not recognized as the same class, analogous to the classloader problem in Java.
Qore 0.8.4 solved this problem by implementing a "class signature" for every user-created class, which is a string describing the interface of the class; the class signature string contains a listing of all the private and public methods and members of the class along with their attributes (types, access control, etc) and the parent classes as well (names and access control plus signatures for user classes and unique class IDs for builtin classes). Then an SHA-1 hash of this string is made, which is used for comparisons for class compatibility.
Before Qore 0.8.4, if the internal unique class ID matched, then the classes were compatible, now, if the class IDs do not match, but the class names and signature match (and the parent Program objects are different), then the classes are assumed to be compatible.
Note that method implementations are not included in the signature, therefore it would be possible to have two classes with the same signature but different implementations, however (aside from hash collisions), assuming the signatures match, the classes should be compatible.
So far the implementation seems to work in practice; there may be some more modifications made to how the signatures are created, but this is a purely internal process in the Qore library and can be changed at any time without affecting backwards compatibility (assuming I only improve the implementation to detect more false positives).
There also could be some security implications in some scenarios, if necessary I can implement a flag to turn this feature off on a per-Program object basis.
Qore has a similar problem; Qore uses the Program class to encapsulate code; multiple Program objects can exist in a program (or script), and classes created from the same source code but from different Program objects were (before Qore 0.8.4) not recognized as the same class, analogous to the classloader problem in Java.
Qore 0.8.4 solved this problem by implementing a "class signature" for every user-created class, which is a string describing the interface of the class; the class signature string contains a listing of all the private and public methods and members of the class along with their attributes (types, access control, etc) and the parent classes as well (names and access control plus signatures for user classes and unique class IDs for builtin classes). Then an SHA-1 hash of this string is made, which is used for comparisons for class compatibility.
Before Qore 0.8.4, if the internal unique class ID matched, then the classes were compatible, now, if the class IDs do not match, but the class names and signature match (and the parent Program objects are different), then the classes are assumed to be compatible.
Note that method implementations are not included in the signature, therefore it would be possible to have two classes with the same signature but different implementations, however (aside from hash collisions), assuming the signatures match, the classes should be compatible.
So far the implementation seems to work in practice; there may be some more modifications made to how the signatures are created, but this is a purely internal process in the Qore library and can be changed at any time without affecting backwards compatibility (assuming I only improve the implementation to detect more false positives).
There also could be some security implications in some scenarios, if necessary I can implement a flag to turn this feature off on a per-Program object basis.
Tuesday, June 12, 2012
Universal References
References have always been a problem in Qore. The main reason was that there was no unified lvalue handling, and also having local variables thread-local meant that there would either have to be no references to thread-local variables, or nonintuitive restrictions (for example by disallowing a reference to an lvalue expression anchored by a thread-local variable to be assigned to a variable with greater scope). Up unti now, Qore took the wimpy way out and only allowed references to be passed to function and method calls.
Today I just committed support for universal references, addressing (another) long-standing deficiency in the language. I was motivated by writing some Qore code that needed to do some reformatting to a complex data structure (that was parsed from XML data - BTW did I ever mention that I prefer YAML?). Basically I needed to add the number of order items to the last order in a list contained which was an attribute of the last record of a list.
The code looked as follows:
# finalize last order - set numberofitems
recs[recs.size() - 1].eventdata.ufulfildespatchconfirm.order[ol.size() - 1].numberofitems = oh.items.size();
recs[recs.size() - 1].eventdata.ufulfildespatchconfirm.order += get_order(e, h);
Today I just committed support for universal references, addressing (another) long-standing deficiency in the language. I was motivated by writing some Qore code that needed to do some reformatting to a complex data structure (that was parsed from XML data - BTW did I ever mention that I prefer YAML?). Basically I needed to add the number of order items to the last order in a list contained which was an attribute of the last record of a list.
The code looked as follows:
# finalize last order - set numberofitems
recs[recs.size() - 1].eventdata.ufulfildespatchconfirm.order[ol.size() - 1].numberofitems = oh.items.size();
recs[recs.size() - 1].eventdata.ufulfildespatchconfirm.order += get_order(e, h);
I was frustrated by Qore's lack of references to simplify the above code. Then I realized that I had the unified lvalue infrastructure (implemented in Qore 0.8.4) and had also solved the thread-local multi-threaded access problem when I implemented closures; when a local variable is bound into a closure, the local variable is not only thread-local anymore; it has a mutual-exclusion lock on it and its lifetime is reference counted - it lasts only as long as it's bound in a runtime closure or until its local scope expires. Because a runtime closure could be used in multiple threads, all local variables bound in the closure when it's created at runtime are protected by the mutual-exclusion lock to ensure consistency and atomicity.
Therefore I simply applied this same approach to local variables that are referenced and removed all restrictions on the use of the lvalue reference operator ('\') in Qore.
The resulting code is cleaner and more consistent, and I actually found a segfault-inducing memory error with the old cludgy implementation at the same time.
The above code now reads:
# finalize last order - set numberofitems
reference orders = \recs[recs.size() - 1].eventdata.ufulfildespatchconfirm.order;
reference lord = \orders[orders.size() - 1];
lord.numerofitems = lord.items.size();
orders += get_order(e, h);
That's two lines longer than the first one without references, but much easier to read, understand, and maintain.
Qore 0.8.5 should be out before too long; I mainly want to get it out so I can update all the binary modules; I discovered a bug in a new library API that is only used by modules built with qpp, so I want to get Qore 0.8.5 out relatively quickly and then update all the binary modules in common use as well.
This feature is already in svn and looks to be stable; the only other feature I plan on adding to Qore for 0.8.5 is support for abstract class methods - this way java-style interfaces can be implemented by defining a class with abstract methods; I've been wanting this for a while, and it doesn't look too hard to do, so I hope to get that done in the next few days.
Tuesday, May 22, 2012
Weak and Strong Destructors
Qore has C++-style constructors and destructors (with a different naming convention; Qore uses "constructor" and "destructor" while C++ uses names derived from the class's name), however due to Qore's unique memory-management approach, while destructors mostly appear to work like in C++, there are some differences in the details of the implementation regarding how destructors work on built-in classes (either from the Qore library itself or from classes provided by binary modules).
The main data structure that represents an object in Qore is the C++ class "QoreObject". Objects of this class have access to their internal state serialized with a mutex (thread-safety is a fundamental design principle of Qore); so for a Qore class implemented only with user code (and not inheriting any built-in classes), after any user destructor method is run, the object is marked as deleted and its internal data structures are cleared and all resources are released back to the operating system. If any copies of references to the object (Qore is like Java in the sense that passing the object by value or assigning the object to another lvalue actually passes/assigns a copy of a reference to the object) are accessed after the object is deleted, an OBJECT-ALREADY-DELETED exception is raised.
This is fairly straightforward and results in predictable behavior for the programmer. Where "weak" and "strong" destructors come in is with built-in classes.
When a user class inherits a built-in class, the built-in class's constructor links a C++ object containing the internal state of the built-in class to the Qore-language object (a "QoreObject" data structure, as mentioned above). Whenever a method of the built-in class is executed on the object, the Qore runtime atomically checks the status of the object to see if it's still valid, then, if so, it finds the built-in C++ object for the built-in class linked to the Qore object (let's call this object a "class state object") and atomically increments its reference count, and then calls the C++ function that implements the method being called with the class state object, a pointer to the "QoreObject" (representing "self") and a (possibly-empty) list of Qore-language arguments to the method call.
When the call is complete, the class state object is dereferenced and the return value of the method call is returned to the caller (or an exception state is returned, if applicable). If the class state object's reference count reaches zero then the class state object is deleted. This normally happens immediately (and synchronously) after the destructor is processed (at which time all linked class state objects are dereferenced), however it can happen afterwards with "weak" destructors.
A "weak" destructor for a built-in object is a destructor that does not implement any further serialization or gated state checking when method calls are made; it just relies on Qore's object state checking. In this case, it's possible for one or more threads to call a method on the object while another thread deletes the object in parallel. If the built-in class does not implement a "strong" destructor, then the built-in class state object will only be deleted after the destructor has been executed (removing the initial reference count for the class state object) and any in-progress methods terminate, which could be quite a while after the actual object destructor has been called depending on the method.
Most built-in classes in Qore implement "weak" destructors because they are simpler to develop and execute faster at runtime (since there's no additional thread synchronization or gating). Furthermore in normal use, a "strong" destructor is not functionally necessary for most classes; it's normally not a problem that such a race condition (where methods are in progress while the object is explicitly deleted in a separate thread) does not cause additional exceptions to be thrown. For example, in Qore the Socket class has a "weak" destructor. However Qore's Queue class has a strong destructor, and, in Qore 0.8.4, the Program class now has a strong destructor in order to enforce stricter discipline on memory resources used.
The Queue class's state object, for example, is implemented by the C++ class "QoreQueue". All methods in this object that cause state changes are explicitly protected by a mutex (logically as this is a thread-synchronization and messaging class). The "strong" destructor grabs the mutex and then checks if other threads are waiting on the Queue (either for reading or writing); if so, an appropriate exception is thrown and the waiting threads are notified that the object has been deleted (and exceptions will be thrown in the waiting threads as well). The race condition with the destructor described earlier is a serious error with the Queue class, particularly due to its critical role in Qore's threading infrastructure, therefore it has a "strong" destructor. The same goes for the Mutex, RWLock, Gate classes, etc.
The Program object now has a "strong" destructor so that whenever it is deleted, all its memory is immediately freed, and any objects or code references created in the Program that have been exported out of the Program will cause PROGRAM-ERROR exceptions to be raised if they try to access the already-deleted Program. This was necessary because otherwise, in a program using lots of dynamically-created Program objects, objects exported from the Programs would cause the parent Program to live for as long as the exported objects even if the Program itself were explicitly deleted if it had a "weak" destructor.
Also on a completely different subject, I implemented support for the "final" keyword when declaring classes and class methods for 0.8.4, which should be the very last feature to go into 0.8.4 before its release, which is now imminent.
The main data structure that represents an object in Qore is the C++ class "QoreObject". Objects of this class have access to their internal state serialized with a mutex (thread-safety is a fundamental design principle of Qore); so for a Qore class implemented only with user code (and not inheriting any built-in classes), after any user destructor method is run, the object is marked as deleted and its internal data structures are cleared and all resources are released back to the operating system. If any copies of references to the object (Qore is like Java in the sense that passing the object by value or assigning the object to another lvalue actually passes/assigns a copy of a reference to the object) are accessed after the object is deleted, an OBJECT-ALREADY-DELETED exception is raised.
This is fairly straightforward and results in predictable behavior for the programmer. Where "weak" and "strong" destructors come in is with built-in classes.
When a user class inherits a built-in class, the built-in class's constructor links a C++ object containing the internal state of the built-in class to the Qore-language object (a "QoreObject" data structure, as mentioned above). Whenever a method of the built-in class is executed on the object, the Qore runtime atomically checks the status of the object to see if it's still valid, then, if so, it finds the built-in C++ object for the built-in class linked to the Qore object (let's call this object a "class state object") and atomically increments its reference count, and then calls the C++ function that implements the method being called with the class state object, a pointer to the "QoreObject" (representing "self") and a (possibly-empty) list of Qore-language arguments to the method call.
When the call is complete, the class state object is dereferenced and the return value of the method call is returned to the caller (or an exception state is returned, if applicable). If the class state object's reference count reaches zero then the class state object is deleted. This normally happens immediately (and synchronously) after the destructor is processed (at which time all linked class state objects are dereferenced), however it can happen afterwards with "weak" destructors.
A "weak" destructor for a built-in object is a destructor that does not implement any further serialization or gated state checking when method calls are made; it just relies on Qore's object state checking. In this case, it's possible for one or more threads to call a method on the object while another thread deletes the object in parallel. If the built-in class does not implement a "strong" destructor, then the built-in class state object will only be deleted after the destructor has been executed (removing the initial reference count for the class state object) and any in-progress methods terminate, which could be quite a while after the actual object destructor has been called depending on the method.
Most built-in classes in Qore implement "weak" destructors because they are simpler to develop and execute faster at runtime (since there's no additional thread synchronization or gating). Furthermore in normal use, a "strong" destructor is not functionally necessary for most classes; it's normally not a problem that such a race condition (where methods are in progress while the object is explicitly deleted in a separate thread) does not cause additional exceptions to be thrown. For example, in Qore the Socket class has a "weak" destructor. However Qore's Queue class has a strong destructor, and, in Qore 0.8.4, the Program class now has a strong destructor in order to enforce stricter discipline on memory resources used.
The Queue class's state object, for example, is implemented by the C++ class "QoreQueue". All methods in this object that cause state changes are explicitly protected by a mutex (logically as this is a thread-synchronization and messaging class). The "strong" destructor grabs the mutex and then checks if other threads are waiting on the Queue (either for reading or writing); if so, an appropriate exception is thrown and the waiting threads are notified that the object has been deleted (and exceptions will be thrown in the waiting threads as well). The race condition with the destructor described earlier is a serious error with the Queue class, particularly due to its critical role in Qore's threading infrastructure, therefore it has a "strong" destructor. The same goes for the Mutex, RWLock, Gate classes, etc.
The Program object now has a "strong" destructor so that whenever it is deleted, all its memory is immediately freed, and any objects or code references created in the Program that have been exported out of the Program will cause PROGRAM-ERROR exceptions to be raised if they try to access the already-deleted Program. This was necessary because otherwise, in a program using lots of dynamically-created Program objects, objects exported from the Programs would cause the parent Program to live for as long as the exported objects even if the Program itself were explicitly deleted if it had a "weak" destructor.
Also on a completely different subject, I implemented support for the "final" keyword when declaring classes and class methods for 0.8.4, which should be the very last feature to go into 0.8.4 before its release, which is now imminent.
Subscribe to:
Posts (Atom)