Global and session objects: Difference between revisions
m (→See also) |
mNo edit summary |
||
Line 742: | Line 742: | ||
<li>[[Copying objects]] | <li>[[Copying objects]] | ||
<li>[[Managing server space for objects]] | <li>[[Managing server space for objects]] | ||
<li>[[Janus SOAP essentials]] | <li>[[Janus SOAP essentials]] | ||
</ul> | </ul> | ||
[[Category:Janus SOAP ULI topics]] | [[Category:Janus SOAP ULI topics]] |
Revision as of 17:45, 11 January 2012
Object %variables are useful for holding references to objects in a request. However, these references “go away” at the end of a request. But it can be very useful to have objects that can be used in multiple User Language requests, that is, in multiple procedure evaluations. For this to be possible, references to objects must be able to span requests.
In Janus SOAP ULI, there are two kinds of objects that span requests: global objects and session objects. Like object variables, global and session objects are not really objects but references to the underlying objects.
A global or session object is referenced by its name (any 1- to 255-byte string), which is kept in a table in CCATEMP that is not cleaned up at the end of request. Since a global/session name allows an underlying object to be referenced after the end of request, an object referenced by a global/session name is not implicitly discarded at end of request.
Just as multiple object variables can reference an underlying object, multiple global/session names can reference the same underlying object. In fact, an object can be referenced by any number of variables and global/session names at the same time. An object can be referenced by request variables, global references, and session references at the same time.
The distinction between a global object reference and a session object reference is that a global reference is tied to the user's login while a session reference is tied to a session. A session can be opened by a logged in user, closed, and then re-opened, by either the same user or by another user. This opening and closing of a session can continue indefinitely, and it can extend beyond the login of the user that created the session. In fact, this latter possibility is one of the common reasons for the use of sessions — to maintain context over several logins by ephemeral logins such as web server logins.
The global and session namespaces are completely separate: it is perfectly valid to have a session object reference called “HAL” and a global object reference with the same name. These references could be to the same underlying object or, more likely, could be references to two totally separate objects, even in different classes.
While it is possible for a session to span multiple logins, it is also possible for a single login to open and close several sessions, though only one can be open at any given time.
You can access an object through its global/session name by:
- A static compile-time binding of the name to a variable
- A dynamic run-time request for a global/session name
Binding global/session names to a variable
The simplest way to use a global name is to bind a variable to it:
%moe is object stooge global('MOE') %larry is object stooge session('LARRY')
These statements indicate that the variable %moe
is to be bound to the global name MOE
and that
the variable %larry
is to be bound to the session
name LARRY
.
This means that, on the first reference to %moe in the request,
the global name MOE is looked up in the global object
table and, if present, assigned to %moe.
If the name MOE is not found in the global object table,
%moe is left as null, its initial value.
After that first reference, any changes to %moe are reflected
in the global object.
Similar processing occurs for variable %larry with the differences that
lookup for the session name is done in the session object table, and if
no session is open, the first reference is a request cancelling error.
The Global and Session qualifiers can be specified on a variable declaration without an explicit name, in which case the name used is the uppercase form of the %variable name, excluding the percent sign. This means that the declarations in the previous example are equivalent to:
%moe is object stooge global %larry is object stooge session
The global and session specified in a Global or Session clause on a variable declaration are automatically converted to uppercase, so the initial declarations above are equivalent to the following:
%moe is object stooge global('moe') %larry is object stooge session('larry')
When compiling in case-insensitive mode, non-quoted
tokens get translated to uppercase before being processed, so the
underlying variable names in the preceding example are really %MOE
and %LARRY
.
Note also that the Object class GetGlobal and SetGlobal methods do not
automatically convert the specified names to uppercase, so if GetGlobal or
SetGlobal are used to access bound global names, the names they use must be in
uppercase.
The Global and Session qualifiers are not allowed on variables in structures or in non-shared Private or Public blocks in class definitions, but they are allowed in variables in a Public Shared or Private Shared block.
Since the Global and Session qualifiers imply Common, an
object variable declared as Global
or Session
in a method or complex subroutine can be accessed
in another method or complex subroutine or in the main program level,
as long as the same implicit or explicit global/session name is used for
all declarations.
Trying to declare the same variable name with different global names
is a compilation error.
Trying to declare different variables bound to the same global name
is also a compilation error.
Obviously, if global and session objects with the same name are to
be referenced in a single request, at least one of them has to be bound
to a variable name different from the global or session name.
The Global and Session qualifiers are allowed on object %variable declarations, whether for objects, collections, enumerations, User Language classes, or system classes. Any attempt to access the same global/session name in different requests using variables of different classes will cause request cancellation. In this case, if the variables are statically bound %variables, the request cancellation occurs on the first reference to the %variable.
Two User Language classes in two requests are considered the same if the classes have the same name and all of the Public and Private non-shared variables have the same types in the same order. All other class elements, including the following, can vary between requests and still be allowed to be accessed as Global by the requests:
- Anything about methods. This includes their absence or presence, parameters, and code.
- Public and private shared variables.
- The names of any variables.
For example, the following class is defined in one request as:
class silly public absurd is float kooky is longstring nutty is fixed dp 2 end public end class ... %test is object silly global
And the class is defined in another request as:
class silly public profound is float solemn is longstring grave is fixed dp 2 end public end class ... %test is object silly global
Since they have the same default global name (TEST, the uppercased variable name) and exactly similar non-shared variables, the classes refer to the same object, so the requests could work with the same underlying object. The objects are no longer the same, however, if the class definition is changed to:
class silly public profound is float solemn is longstring grave is fixed dp 3 end public end class ... %test is object silly global
The third public variables in the classes now differ in datatype
(DP 3
instead of DP 2
), so they cannot share a global object.
The following class is also not allowed to share a global name with
the Silly
class
because the public variables are in a different order:
class silly public grave is fixed dp 2 solemn is longstring profound is float end public end class ... %test is object silly global
If a global or session object's class contains object variables for other classes, those classes, too, must match from request to request for the global or session object to be accessible from all the requests.
In general, it is recommended that you keep as similar as possible any classes that are referred to among multiple requests using global/session names. It makes most sense to vary between requests only the methods that are actually defined for the class — and to keep program size down, define only the methods that will be used in a request.
Public and Private Shared variables are not saved in the object that underlies a global name, so they are not preserved from request to request. In fact, as noted before, shared variables are allowed to be different from request to request even if individual object instances of the class are accessed in all those requests via a global or session name.
A file or group object (see "File classes") associated with a global/session name will be implicitly discarded if the file or group is closed between requests.
Using Object class methods to access global and session objects
In addition to binding to a variable, the other way to access global/session names is to use shared methods in the Object class. The Object class can be thought of us the base class for all objects. It is possible to define an object in the Object class:
%thingy is object object
But there is not much one can do with an Object class object other
than to use it as a handle to access shared methods.
As such, if using dynamic globals, it might make sense to have a
standard Common object variable called %object
:
%object is object object common
The following shared methods are available for the Object class:
DiscardGlobal | %(Object):DiscardGlobal(name) Discards the object referenced by name. If the indicated global name does not reference an object, this method does nothing. |
---|---|
DiscardGlobals | %(Object):DiscardGlobals(wildcard) Discards the objects referenced by the name that matches the specified wildcard string. If no global name references an object, this method does nothing.
If the wildcard string is |
DiscardSession | %(Object):DiscardSession(name) Discards the object referenced by name. If the indicated session name does not reference an object, this method does nothing. If no session is open, the request is cancelled. |
DiscardSessions | %(Object):DiscardSessions(wildcard) Discards the objects referenced by the name that matches the specified wildcard string. If no session name references an object, this method does nothing.
If the wildcard string is If no session is open, the request is cancelled. |
GarbageCollect | %(Object):GarbageCollect Performs garbage collection, that is, cleans up “unreachable” objects. For more information about garbage collection, see "Managing object storage". |
GetGlobal | %(Object):GetGlobal(name, %variable) Sets the variable indicated by %variable to reference the object referenced by the global name name. If the indicated global name does not reference an object, this method sets the indicated variable to null. %variable cannot be an object member. |
GetSession | %(Object):GetSession(name, %variable) Sets the variable indicated by %variable to reference the object referenced by the session name name. If the indicated session name does not reference an object, this method sets the indicated variable to null. %variable cannot be an object member. If no session is open, the request is cancelled. |
GlobalList | %persObjList = %(Object):GlobalList(matchString) Returns to an Arraylist of Object PersistentObjectInfo information about all the global objects whose names match a specified pattern string. |
NullifyGlobal | %(Object):NullifyGlobal(name) Sets the object reference named name to null, that is, referencing nothing. If the specified global name does not reference an object, this method does nothing. |
NullifyGlobals | %(Object):NullifyGlobals(wildcard) Sets object references with names matching the specified wildcard string to null. If no global name references an object, this method does nothing.
If the wildcard string is |
NullifySession | %(Object):NullifySession(name) Sets the object reference named name to null, that is, referencing nothing. If the specified session name does not reference an object, this method does nothing. If no session is open, the request is cancelled. |
NullifySessions | %(Object):NullifySessions(wildcard) Sets object references with names matching the specified wildcard string to null. If no session name references an object, this method does nothing.
If the wildcard string is If no session is open, the request is cancelled. |
SetGlobal | %(Object):SetGlobal(name, object) Sets the global reference named name to the object referenced by object. object can be a %variable, a structure member, or a class member inside an object. |
SetSession | %(Object):SetSession(name, object) Sets the session reference named name to the object referenced by object. object can be a %variable, a structure member, or a class member inside an object. |
SessionList | %persObjList = %(Object):SessionList(matchString) Returns to an Arraylist of Object PersistentObjectInfo information about all the session objects whose names match a specified pattern string. |
The following code illustrates how two globals are set:
%moe is class stooge %larry is class stooge %curly is class stooge ... %(object):setGlobal('StoogeHoward', %moe) %(object):setGlobal('StoogeFine', %larry) %(object):setGlobal('StoogeHoward', %curly)
These globals can then be retrieved in a subsequent request:
%zeppo is class stooge %groucho is class stooge %chico is class stooge ... %(object):getGlobal('StoogeHoward', %zeppo) %(object):getGlobal('StoogeFine', %groucho) %(object):getGlobal('StoogeHoward', %chico)
The following would discard any session objects referenced by a name that begins with “Stooge”:
%(object):discardSessions('Stooge*')
There is no distinction between a global/session name that references a Null object and a global/session name that simply is not set. When a global or session name is not set (or Null), there is no validation of the class on a GetGlobal or GetSession (or retrieval via a bound reference).
For example, the following code is valid:
%moe is class stooge %list is class stringList %(object):setGlobal('LIST', %moe) %(object):getGlobal('LIST', %list)
Even though the global name “LIST” was set to reference a Stooge
class object, the input object reference was null (%moe
was not instantiated),
so the global name was unset.
Since the global name was not set prior to the GetGlobal call,
the GetGlobal call simply set %list
to Null.
The NullifyGlobal and NullifySession methods are equivalent to SetGlobal and SetSession calls with Null for the object input. That is, the following two lines of code are equivalent:
%(object):nullifyGlobal('Vino') %(object):setGlobal('Vino', null)
Managing object storage
There are three basic models for object storage management in object-oriented programming environments:
- Application managed
- Reference count
- Garbage collection
The application cleans up
Application storage management means that it is an application's responsibility to clean up objects that are no longer needed. This model is not used in many object-oriented languages, with the notable exception of C++ and its derivatives, because it places an unnecessary burden on application programs.
Still, many languages allow an application to do object storage management if it wants. This typically involves provision of a method to explicitly discard an object when it is no longer needed. The Janus SOAP ULI is no exception to this: it provides a Discard method to allow an object to be discarded when it is no longer needed.
Cleanup based on object reference count
In the reference count model, a count of references to an object is maintained, and when that count goes to zero, the object is discarded. This model works well because objects are generally discarded as soon as they are “unreachable.” That is, if there are no object variables that directly or indirectly reference an object, the object is essentially unusable and so can be discarded.
By discarding objects as soon as they are unreachable, the reference count model tends to facilitate applications that run in a small “footprint,” that is, use minimal storage. In addition, this model tends to ensure smooth response and to minimize the “burps” and excessive CPU overhead associated with garbage collection. For all these reasons, the Janus SOAP ULI implements the reference count model of object management.
It is quite common for there to be only a single reference to an object. With the reference count model, when that single reference is lost, the object can immediately be discarded. For example, consider the following:
%sl is object stringList ... for %i from 1 to %someLargeNumber %sl = new %sl:add('First item in iteration ' with %i) %sl:add('Second item in iteration ' with %i) %sl:print end for
While not very interesting, this illustrates a key point.
On the first iteration of the loop, a Stringlist object instance
is created (instantiated).
On the second iteration, a new Stringlist object is instantiated.
But the only way to reference the object created in the first iteration
was via %sl
, and %sl
now references the object
instantiated in the second iteration.
The reference count for the first object goes to zero, so
the object is automatically discarded.
This process is repeated at each iteration: the object
instantiated in the previous iteration is discarded when %sl
is
set to reference the new instance.
Thus with no effort on the programmer's part, the Janus SOAP ULI ensures that there is never more than one instance of a Stringlist object in this code.
One drawback of the reference count model is that there might be a rarely used reference to an object “laying around,” and this prevents an object from being discarded when it should. There is not much to be done about this, as there is no way for the Janus SOAP ULI to “know” that an application is really done with an object should this occur. Fortunately, it is rare that a reference will lay around long without being assigned to something else, eliminating the last reference to an object, and so causing it to be cleaned up (discarded). If, however, this is a concern, an application should explicitly discard an object when it is no longer needed.
A larger problem with the reference count model is that it is largely incapable of dealing with cycles. A cycle is a chain of circular references where, for example, object A contains a reference to object B which contains a reference back to object A. In such a case, each of these circular references count as a reference, so they keep the objects from being discarded, even if there are no other references to the objects, because the objects are unreachable.
Cycle example
Consider, for example, the following:
class circular public variable next is object circular variable number is float end public end class %a is object circular %b is object circular %a = new %a:number = 1 %b = new %b:number = 2 %a:next = %b %b:next = %a
The object pointed to by %a
(call it ObjectA) has a reference
count of two — one for the reference via %a
and
another via %b
.
And the object pointed to by %b
(call it ObjectB) has a reference
count of two — one for the reference via %b
and another via %a
.
Now suppose this statement is executed:
%b = null
ObjectB is no longer reachable from %b
, but it can still be accessed
via %a
, so its reference count is one and it is not discarded.
ObjectA is still referenced by %a
and it also has a reference from
ObjectB, so its reference count is still two.
Now suppose this statement is executed:
%a
= null
Since %a
no longer references ObjectA, ObjectA's reference count
goes to one (the reference in ObjectB).
And ObjectB's reference count is also one (the reference in ObjectA).
But clearly neither ObjectA nor ObjectB can be accessed any more
in the request.
Such an unreachable cycle is sometimes called an orphaned cycle.
Obviously, the scenarios involving cycles can get arbitrarily complex, where objects contain many references to other objects in the same and different classes. There is even a simpler scenario where an object can refer to itself, creating a cycle of one.
Fortunately, the Janus SOAP ULI cleans up all objects, regardless of their reference count at end of request, since presumably all references to the objects have gone away with the end of request. Unfortunately, for all their benefits, global and session objects muddy this picture. Because a global or session name is simply a reference to an object, it increases the reference count by one. But, unlike %variable references to objects, a global or session reference does not “go away” at end of request.
So, in the previous example, if before setting %a
and %b
to null,
this statement executed:
%(object):setGlobal('ELLIPTIC', %a)
ObjectA's reference count would be incremented by one.
Furthermore, even if %a
and %b
were set to null,
you could
access ObjectA (and, by extension, ObjectB) in the same or a different
request using GetGlobal (or a statically bound variable).
So, if there are any global or session objects at the end of request, it is possible that any object with a non-zero reference count might be accessible directly or indirectly from a global or session reference. As such, the presence of any global or session objects extends the reference count model of object management across request boundaries: that is, objects are not discarded at end of request unless their reference count goes to zero.
The solution to this problem with the reference count model is garbage collection.
Cleanup by garbage collection
In a pure garbage collection based management approach, references to objects are not tracked; instead, unreachable objects are periodically cleaned up. This cleanup process is known as garbage collection.
The garbage collection process involves these steps:
- Going through every static reference to objects — variables in most languages; variables and global and session objects in the Janus SOAP ULI.
- Marking as “reachable” every object accessible via these static references.
- Following any references to other objects inside these objects, and marking those objects as reachable.
- Marking as reachable any objects referred to by any object marked reachable, until all objects directly or indirectly accessible from a static reference are marked reachable.
- Scanning all objects, and discarding the unreachable ones.
As one might imagine, the garbage collection process can be quite expensive, though the cost is proportional to the number of objects, so it does not grow any faster than the number of objects.
Because the Janus SOAP ULI uses the reference count model of object management, garbage collection is typically unnecessary. Possible exceptions, however, are requests that create objects with cycles (or potential cycles, anyway). For these, garbage collection might be necessary if:
- A request runs for a long time, possibly accumulating many orphaned cycles.
- A thread has one or more global or session references, preventing the Janus SOAP ULI from cleaning up all objects at end of request, regardless of their reference count.
If either of these conditions is present, a request that is concerned about the storage used by objects should do either of the following:
- Explicitly discard objects that are part of cycles that are about to be orphaned.
- Periodically run garbage collection.
Explicitly discarding
It is sometimes sufficient to explicitly discard a single object in an about-to-be-orphaned cycle to discard the whole cycle. If in the cycle example, you specified
%a:discard
instead of just setting %a
to null, not only would ObjectA
have been discarded,
but, because ObjectA's reference to ObjectB would have been eliminated, ObjectB
would also have been discarded.
Or, even more directly, you could initially deep discard an object to more reliably discard the entire cycle:
%a:deepdiscard
Invoking garbage collection
If there are no obvious or simple places to clean up cycles that are about to be orphaned, the only solution is to do garbage collection.
One way an application does garbage collection is to explicitly specify as needed the GarbageCollect method of the Object class:
%(object):GarbageCollect
An alternative way to do garbage collection is to implicitly issue the the GarbageCollect method. You can set the user parameter AUTOGCN (added in Sirius Mods 7.5) to invoke garbage collection automatically based on the count of non-global, non-session objects that have not been discarded by normal object cleanup.
Whether explicit or implicit garbage collection, the user parameter GCSTATS (also added in Sirius Mods 7.5) can assist you by producing a message that contains the results of each garbage collection.
How often and when you need to invoke garbage collection is an application issue, but some things to keep in mind when making the decision as to when to run garbage collection are:
- Garbage collection can be fairly CPU-intensive.
- Garbage collection is totally unnecessary unless you have orphaned cycles.
- The benefit of cleaning up orphaned cycles is that resources associated with those cycles are cleaned up. This could be CCATEMP space for the objects themselves, CCATEMP space for object contents, record locking table space, record locks, Janus Sockets threads, etc. How critical it is to quickly release these resources depends, of course, on the resources.
- All objects are cleaned up at user logout after any implicit session close caused by the logout.
- Garbage collection is performed automatically at session close time if there are any session object references.
To better understand this last point, it is worth going into more detail about session close processing.
Session close processing
In most basic terms, when a session is closed, all objects that can
be accessed either directly or indirectly from the session names
are made inaccessible to the closing thread, so those objects
can be accessed by whatever thread might open the session.
Returning to the cycle example,
with the difference that
%a
is a session object (assume the session is already open):
class circular public variable next is object circular variable number is float end public end class %a is object circular session %b is object circular %c is object circular %a = new %a:number = 1 %c = %a %b = new %b:number = 2 %a:next = %b %b:next = %a
Then issue:
$session_close
ObjectA ""follows the session," so it is no longer accessible in
the request.
Therefore, after a $session_close, the following results in a
null pointer error,
since %c
references ObjectA which is no longer available in the request.
print %c:number
Perhaps somewhat more surprisingly, the following also results in a null pointer error:
print %b:number
This error occurs because ObjectB was accessible via ObjectA (which was accessible via a session name), so ObjectB is considered to be "owned" by the session: when the session is closed, ObjectB "follows the session," so it becomes inaccessible in the request.
The processing that occurs at session close time is very similar to garbage collection:
- All objects accessible from session names are marked as session-reachable.
- As with garbage collection, references inside session-reachable objects are followed, marking any objects referred to inside session-reachable objects as also session reachable. This process is continued until all objects that might be reachable from a session name have been marked as such.
- All objects are scanned, and those that are not session-reachable remain in thread-specific structures. Those that are session-reachable are moved to a session-specific structure (in CCATEMP).
Because this process is relatively expensive and very similar to garbage collection, the Janus SOAP ULI performs garbage collection whenever it does session close processing, since it is much more efficient to do both at once rather than to do each individually. Therefore, and simply, garbage collection is unnecessary after a $Session_Close.