Global and session objects

From m204wiki
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

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 SOUL requests, that is, in multiple procedure evaluations. For this to be possible, references to objects must be able to span requests.

In SOUL, 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-defined 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-defined 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 system class methods to access global and session objects

Using the Object class

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:

MethodDescription
DiscardGlobalDiscard a global object
DiscardGlobalsDiscard global objects
DiscardSessionDiscard a session object
DiscardSessionsDiscard session objects
GetGlobalRetrieve global object reference
GetSessionRetrieve session object reference
GlobalListGet list of global objects
NullifyGlobalClear global object reference
NullifyGlobalsClear global object references
NullifySessionClear session object reference
NullifySessionsClear global object references
SessionListGet list of session objects
SetGlobalSet global object reference
SetSessionSet session object reference

The following code illustrates how 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)

Using the PersistentObjectInfo class

The PersistentObjectInfo class contains information about global or session objects in the current thread. PersistentObjectInfo objects offer the advantage of the sorting, finding, and subsetting facilities of collections.

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. SOUL 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, SOUL 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, SOUL 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 SOUL 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, SOUL 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:

  1. Going through every static reference to objects — variables in most languages; variables and global and session objects in SOUL.
  2. Marking as "reachable" every object accessible via these static references.
  3. Following any references to other objects inside these objects, and marking those objects as reachable.
  4. 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.
  5. 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 SOUL 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 SOUL 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 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 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:

  1. All objects accessible from session names are marked as session-reachable.
  2. 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.
  3. 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, SOAP 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.

See also