FORTH-ICS / TR 381 July 2006 'An Informal Proof on the Contradictory
Total Page:16
File Type:pdf, Size:1020Kb
FORTH-ICS / TR 381 July 2006 ‘An Informal Proof on the Contradictory Role of Finalizers in a Garbage Collection Context’ Anthony Savidis ABSTRACT Garbage collection in OOP languages provides facilities to hook code that is executed upon object finalization. Semantically, the point in time that finalizer code is executed is not determined by the application logic, but by the garbage collection system. This fact renders a potential mismatch, since application-specific code, i.e. the finalizer implementation, normally affecting program state and control flow, is called automatically at a point in time that is indifferent to the application semantics. Although an analogous situation is observed in event-based systems, since event processors are called-back asynchronously by the underlying system, there is a fundamental difference: while event generation is essentially nondeterministic, originated from external event sources, object destruction is a semantic action that is always decided by applications. In summary, the mismatch is that although applications decide if and when destruction occurs, the garbage collector is responsible to commit the relevant code. We prove that this mismatch is due to the contradictory presence of finalizers in a garbage collection context. Page 1 Introduction Preface This technical report has been also motivated by requests from colleagues to report the informal proof for the contradictory role of finalizers in garbage collection, simply relying on informal common software engineering principles. Additionally, the reporting has been also motivated by arguments that came to my attention via personal communication that either the claim cannot be proved (I assume that “cannot prove” arguments are subject to formal proofs, so such arguments are more weak than the claim they attack), or that the claim is trivial requiring essentially no proof (intuitively true, but does not explain why Java, C#, Python, just to name a few languages, still adopt finalizers). Throughout this brief report we use the term “object cancellation” as a synonym to destruction, though without the memory disposal connotation. Our proof relies on the thesis that application objects are cancelled only due to conditions inherent in the application semantics, being evaluated as part of the program control-flow. As we discuss at the end, although there is an exception to this rule, it is better handled with a programming wrap-around rather than by relying on finalizers. Problems with finalizers and the severe confusion they may cause due to interference of the garbage collector with the application semantics in unpredictable ways are very well known, not limited only to novice programmers,. A few representative articles are provided in the references section [1,2,3,4,5,6,7,8] being indicative of the relevant problem. Since we are not aware of a proof or more elaborated statement exposing the contradictory role of finalizers in a garbage collection context, we provide one in this report. Primary thesis Objects are cancelled at runtime if and only if the application state implies that the relevant destruction conditions are met, decided always deterministically and as part of the application semantics within the normal program control flow. A. Savidis, 2006, Finalizers and garbage collection, TR_381_2006, ICS-FORTH Page 2 Following this position, all corresponding cancellation actions should be appropriately orchestrated by the initiative of the application, while such actions are expected to apply at runtime the particular application policy for instance cancellation. Additionally, these application actions may be committed instantly or later, depending on the application logic itself. On the other hand, automatic memory disposal usually takes place only when there is no external reference (i.e. program variable) to an object. In this sense, the logic for automatic memory disposal being system initiated is semantically indifferent to the application logic for object cancellation. Next we formulate our basic argument, followed by the proof that automatic garbage collection and finalizers semantically contradict. Argument Finalizers are not needed or else garbage collection becomes equivalent to manual memory management, aka, finalizers semantically contradict with the notion of garbage collection. Proof of the argument The proof is applied by abduction. Let’s assume that the language provides a finalizer function where programmers may encapsulate code to be called just before the memory of an object is automatically collected. In this case there are two possible programming alternatives: I. Either programmers totally dismiss the finalizer function, handling cancellation manually when the application-oriented conditions are met, calling special- purpose cancellation member functions; II. Or programmers utilize the finalizer to inject code that actually implements the application-oriented cancellation logic. If case (I) holds our argument becomes self-evident. However, if case (II) holds there are two possible options: a. The application logic deterministically requires that instance cancellation actions occur when it is decided to do so, and only if the relevant semantic conditions are met. Consequently, to accommodate this demand, programmers should explicitly A. Savidis, 2006, Finalizers and garbage collection, TR_381_2006, ICS-FORTH Page 3 write code to cause the garbage collector instantly collect the instance, such as by nullifying the last remaining references, at the exact points of code where the control flow implies that the cancellation conditions are met. Notably, it may not always be certified that collection will follow immediately reference nullification, meaning that finalizers are not a guaranteed method for implementing cancellation policies with precise timing. b. Else, it is of no semantic importance as to when the cancellation actions may actually take place. Apparently, it does not actually matter at what point during runtime the finalizer blocks are committed. In the (a) case, the automatic garbage collection benefit practically evaporates, since explicitly nullifying instance references, to actually cause object collection and finalizer invocation, is equivalent to explicit memory disposal, i.e. delete in languages without garbage collection. Hence, the automatic garbage collection becomes essentially analogous to manual memory disposal. In the (b) case, if the runtime point in which cancellation actions are performed is indifferent to the application semantics, we may execute the cancellation code exactly after normal application termination, meaning the cancellation actions become semantically nop to the application logic itself. However, in such a case, the semantic effect of the finalizer is apparently equivalent to that of an empty finalizer, meaning no finalizer is essentially needed. Resolving exceptions A viable exception to our thesis accounts to a very specific scenario where object finalizations should take place outside of the normal program control-flow: dynamic allocation of instances, within blocks that may be partially executed when exit is caused due to exception handling (i.e. stack unwinding). For instance, let’s consider the following code snippet, representative of blocks that encompass the instantiation, use and cancellation of instances of various classes. For clarity, we assume a single instance. A. Savidis, 2006, Finalizers and garbage collection, TR_381_2006, ICS-FORTH Page 4 f() { X x = new X Use(x) Cancel(x) return whatever; } In this example, if the execution of the statements following the creation of x, including Use(x) but before Cancel(x), causes an exception that is not handled before Cancel(x), the function will be exited without ever calling Cancel(x). Hence, without the support of finalizers in garbage collection, it may look that there is no way to ensure in such a scenario that the code for cancelling x is always called before x gets collected. Next we prove by implementation the trivial, that finalization even for such exceptional situations can be programmed in a straightforward manner. Let’s consider an alternative implementation shown below, reflecting programmer’s awareness of the fact that exceptions may be raised due to calls made inside this function. f() { X x = new X try { Use(x) } catch Exception e { Cancel(x) rethrow; Å Abnormal function exit with exception propagation } Cancel(x) return whatever; Å Graceful function return } The extra code serves graceful cancellation, handling any type of exception followed by immediate re-raising for subsequent handling by an appropriate calling function. Effectively, this seems to turn the updated version of our code without finalizers equivalent to the previous one assuming finalizers are supported. The only apparent issue concerns the required duplication of the cancellation statement Cancel(x). However, once our scenario is generalized to assume multiple instances that may be created and used locally within a block, we may observe that it is not certified that all such instances would have been created when an exception is raised. Hence, we should manage to perform cancellation calls within the catch block only for those instances that were actually instantiated. Finally, we should cope with the possibility A. Savidis, 2006, Finalizers and garbage collection, TR_381_2006, ICS-FORTH Page 5 that instantiations may naturally raise exceptions, meaning we have to place new expressions within the basic try block. Reflecting