-
Notifications
You must be signed in to change notification settings - Fork 97
/
destruct.txt
120 lines (105 loc) · 5.95 KB
/
destruct.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
Destructors
===========
Destructors are special methods executed just before the object
will be destroyed. It means that a programmer _has_to_ pay special
attention to the destructor's code and _NEVER_ store the reference
to SELF object in external items. The piece of memory where
the instance of class (object) is held will be freed when
the destructor finishes, so any references to SELF object will
point to uninitialized memory or memory allocated for other
structures. Sooner or later (probably in a next GC pass) these
references will be accessed, causing GPFs or some other unpredictable
problems.
Harbour implementation
======================
General destructor activation
-----------------------------
Each object item has a reference counter. When it reaches 0 the object
is destroyed and if it has destructor message then this message will be
executed just before freeing the memory. After executing destructor HVM
checks if a programmer didn't store the reference to the object being
destroyed somewhere, and if he did then RT error is generated.
It's possible to detect such situation by simply checking the reference
counter. Such situation is not dangerous for HVM integrity because the
memory is not freed - instead the object is converted to an empty array.
Though it does not mean that the application is valid. A programmer stored
a reference to SELF object somewhere and when he will try to access it as
an object, some other RT errors will be generated.
For sure such programs have to be fixed.
It's the not the only one situation when destructors can be executed.
It's possible to create cyclic references between some complex items
so the reference counters will never reach 0 even if the items are not
longer accessible by application. To avoid memory leaks, such items are
destroyed by Garbage Collector in a special way. GC scans all items known
to HVM and marks them as used, then destroys all items which are not marked.
The reference counters in such items are greater then zero and cannot
be directly used to detect bugs in a user code. So GC collects all
inaccessible items and then executes cleanup functions for each of them,
and finally checks if reference counters reached zero before it will
free the memory blocks. If they didn't then RT error is generated for
the first memory block. All items which are still accessible, are not
freed and if GC can recognize a type of an item then it will also try to
convert it to some empty form (e.g. empty array).
The destructors are executed from cleanup functions so they all will be
executed and then, if there is something wrong, RT error will be generated
for the first memory block which was copied to some external structures.
Please note that the order in which destructors are executed by GC
can be different then some logical order defined by an application. HVM
does not know anything about programmer's ideas so a programmer has to
create a code which will be safe for such situations. HVM only guaranties
that destructors will be executed only once for each object.
This also cannot break HVM integrity for standard object items which are
represented as arrays. But if the problem is inside cleanup function of
a GC POINTER item, which has a structure unknown to HVM, then any further
behavior can be unpredictable if a programmer, who created such pointer
items, doesn't support such situation himself in his C code. It's a good
practice to add some type of marker to body of memory allocated by
hb_gcAlloc() to detect bugs in .prg code destructors which may keep
pointers to freed POINTER item (these could be destructors of differ
object items).
In such case GC will not free the block so cleanup function (not object
destructor) will be executed second time when the buggy reference will be
cleared. Such marker can help to make clean-up function safe for such
situation. It's a programmer's implementation decision if such pointer item
should be still valid and work like before or drop its capacities after
first cleanup function execution. Anyhow the code should expect such
situation.
In summary, Harbour destructor implementation should be able to detect
bugs in destructor and keep HVM integrity. But we are not able to
guarantee that nothing wrong will happen with 3rd party code which
uses POINTER items scanned by GC, which are not safe for .prg code
bugs in destructors and repeated cleanup function execution.
Exiting the application and HVM closing
---------------------------------------
When HVM exits all items on HVM stack (local variables and parameters)
are cleaned. Then HVM clears all memvar items.
After these two steps HVM executes GC and all items which are not longer
accessible will be freed. Then HVM closes RDD system.
It's the last moment when object destructors can be executed, because
in next steps, the classy subsystem is closed and all static variables
cleared. So for all items which still exist as STATIC variables or in
some other structures, the object destructors will not be executed.
Clearing STATIC variables before closing classy subsystem will not help
because STATIC variables are integral part of this subsystem.
Anomalies and exceptions
------------------------
In some situations HVM may clear items when exception appear, e.g.
BREAK or QUIT request. In such case executing the exception type
is stored and destructors are executed and finally the exception
restored. But in destructors code new exception can appear. In such
case HVM will give higher priority to QUIT request. If both exception
are BREAK then the one from destructor is taken because it could
overwrite the error object created before destructor.
Inheritance
-----------
If class has more then one destructor inherited from other classes
then all destructors are executed in reverted order. First current
class destructor (if any) and then super class destructors.
Defining destructors in CLASS definition code
---------------------------------------------
CREATE CLASS ...
...
DESTRUCTOR <MethodName>
...
ENDCLASS
Przemyslaw Czerpak (druzus/at/priv.onet.pl)