4. Writing User Actions

Previous chapter

Next chapter

Within a GemStone Smalltalk-based application, you may choose to write a C function for certain operations, rather than to perform the work in GemStone. For example, operations that are computationally intensive can be written as C functions and called from within a Smalltalk method (whose high-level structure and control is written in Smalltalk). This approach is similar to the concept of “user-defined primitives”.

This chapter describes how to implement C user action functions that can be called from GemStone, and how to call those functions from a GemBuilder application or a Gem (GemStone session) process.

Some of the functionality provided by User Action libraries can instead be supported using the Foreign Function Interface (FFI) in GemStone Smalltalk, which allows you to invoke functions in C libraries directly from Smalltalk, without compiling C code. The FFI is described in Programming Guide.

4.1  Shared User Action Libraries

Although user actions can be linked directly into an application, they are usually placed in shared libraries so they can be loaded dynamically. The contents of a library are not copied into the executable. Instead, the library’s main function loads all of its user actions. Only one copy is loaded into memory, even if multiple client processes use the library.

User action libraries are used in two ways: They can be application user actions, which are loaded by the application process, or session user actions, which are loaded by the Gem session process. The libraries themselves are the same for both, it is the operation that is used to load the library determines which type it is.

Application user actions are the traditional GemStone user actions. They are used by the application for communication with the Gem or for an interactive interface to the user.

Session user actions add new functionality to the Gem, something like the traditional custom Gem. The difference here is that you only need one Gem, which can customize itself at run time. It loads the appropriate libraries for the code it is running. The decisions are made automatically within GemStone Smalltalk, rather than requiring the users to decide what Gem they need before they start their session.

4.2  How User Actions Work

User Actions are invoked from GemStone Smalltalk. This can be done from GemStone Smalltalk application code, topaz execution, etc, or using GemBuilder for C by calling a function such as GciExecuteStr or GciPerform.

Before it can be used, the user action library must be loaded and initialized; see Loading User Actions.

1. GemStone Smalltalk code invokes a user action function (written in C) by sending a message of the form:

System userAction: aSymbol with: args

The args arguments are passed to the C user action function named aSymbol.

2. The C user action function can call any GemBuilder functions and any C functions provided in the application or the libraries loaded by the application (for application user actions), or provided in the libraries loaded by the Gem (for session user actions).

If a GemBuilder or other GemStone error is encountered during execution of the user action, control is returned to the Gem or your GemBuilder application as if the error had occurred during the call to execute the code.

3. The C user action function must return an OopType as the function result, and must return control directly to the Smalltalk method from which it was called.

Results are unpredictable if the C function uses GCI_LONGJMP instead of returning control to the GemStone Smalltalk virtual machine.

4.3  Developing User Actions

For your GemStone application to take advantage of user action functions, you do the following:

Step 1. Determine which operations to perform in C user action functions rather than in Smalltalk, and write the user action functions.

Step 2. Create a user action library to package the functions.

Step 3. Provide the code to load the user action library.

If the application is to load the library, add the loading code to your application.

If the session is to load the library, use the GemStone Smalltalk method System class>>loadUserActionLibrary: for loading.

Step 4. Write the Smalltalk code that calls your user action. Commit it to your GemStone repository.

Step 5. Debug your user action.

The following sections describe each of these steps.

1.  Write the User Action Functions

Writing a C function to install as a user action called from Smalltalk is much the same as writing other C functions. However, since the application that called the user action (whether written in C, Java, or Smalltalk) controls the export set—the set of OOPs to save after execution completes—user actions do not solely control the way objects are preserved from garbage collection.

For more on the issue of retaining references to newly created objects and risk of premature garbage collection, see the discussion underGarbage Collection.

To enable objects to be retained, user actions maintain a separate export set that is specific to the user action, in addition to the PureExportSet. When objects are automatically added to an export set or added using GciSaveObjs, they are put into the user action’s export set rather than into the PureExportSet.

When the user action function completes, its export set is cleared. Don’t save the OOPs in static C variables for use by a subsequent invocation of the user action or by another C function.

To make a newly created object a permanent part of the GemStone repository, the user action has two options:

  • Store the OOP of the new object into an object known to be permanent, such as a persistent collection that the application looked up in the GemStone repository, or that was provided as an argument to the user action function.
  • Return the OOP of the object as the function result. After a user action returns, the persistence of the new object is determined by the normal semantics of the calling application.

To create a reference that prevents garbage collection but is not shared between sessions, you may use GemStone Smalltalk code to add them to the user-definable portion of System sessionState using System >> sessionStateAt:put:. Objects referenced in this way will be preserved until the session terminates, at which point they will be subject to garbage collection if there are no other references.

2.  Create a User Action Library

Whether you have one user action or many, the way in which you prepare and package the source code for execution has significant effects upon what uses you can make of user actions at run time. It is important to visualize your intended execution configurations as you design the way in which you package your user actions.

To build a user action library:

1. Include gciua.hf in your C source code.

2. Define the initialization and shutdown functions.

3. Compile with the appropriate options, as described in Chapter 5.

4. Link with gciualib.o and the appropriate options, as described in Chapter 5.

5. Install the library in the $GEMSTONE/ualib directory.

The gciua.hf Header File

User action libraries must always include the gciua.hf file, rather than the gci.hf or gcirtl.hf file. Using the wrong file causes unpredictable results.

The Initialization and Shutdown Functions

A user action library must define the initialization function GciUserActionInit and the shutdown function GciUserActionShutdown.

Do not call GciInit, GciLogin, or GciLogout within a user action.

Defining the Initialization Function

Example 4.1 shows how the initialization function GciUserActionInit is defined, using the macro GCIUSER_ACTION_INIT_DEF. This macro must call GciDeclareAction once for each function in the set of user actions.

Example 4.1 

static OopType doParse(void)
  return OOP_NIL;
static OopType doFetch(void)
  return OOP_NIL; 
  GciDeclareAction("doParse", doParse, 1, 0, TRUE);
  GciDeclareAction("doFetch", doFetch, 1, 0, TRUE);
  // ...

GciDeclareAction associates the Smalltalk name of the user action function userActionName (a C string) with the C address of that function, userActionFunction, and declares the number of arguments that the function takes. A call to GciDeclareAction looks similar to this:

GciDeclareAction("userActionName", userActionFunction, 1, 0, TRUE)

The function installs the user action into a table of such functions that GemBuilder maintains. Once a user action is installed, it can be called from GemStone.

The name of the user action, “userActionName”, is a case-sensitive, null-terminated string that corresponds to the symbolic name by which the function is called from Smalltalk. The name is significant to 31 characters. It is recommended that the name of the user action be the same as the C source code name for the function, userActionFunction.

The third argument to GciDeclareAction indicates how many arguments the C function accepts. This value should correspond to the number of arguments specified in the Smalltalk message. When it is 0, the function argument is void. Similarly, a value of 1 means one argument. The maximum number of arguments is 8. Each argument is of type OopType.

The fourth argument to GciDeclareAction is rarely used. The final argument indicates whether to return an error if there is already a user action with the specified name.

Your user action library may call GciDeclareAction repeatedly to install multiple C functions. Each invocation of GciDeclareAction must specify a unique userActionName. However, the same userActionFunction argument may be used in multiple calls to GciDeclareAction.

Defining the Shutdown Function

The shutdown function GciUserActionShutdown is defined by the GCIUSER_ACTION_SHUTDOWN_DEF macro. GciUserActionShutdown is called when the user action library is unloaded. It is provided so the user action library can clean up any system resources it has allocated. Do not make GemBuilder C calls from this function, because the session may no longer exist. In fact, GciUserActionShutdown can be left empty. Example 4.2 shows a shutdown definition that does nothing but report that it has been called.

Example 4.2 

#include "gciuser.hf"
  /* Nothing needs to be done. */ 
  fprintf(stderr, "GciUserActionShutdown called.\n");

Compiling and Linking Shared Libraries

Shared user actions are compiled for and linked into a shared library. See Chapter 5, “Compiling and Linking” for instructions.

Be sure to check the output from your link program carefully. Linking with shared libraries does not require that all entry points be resolved at link time. Those that are outside of each shared library await resolution until application execution time, or even until function invocation time. You may not find out about incorrect external references until run time.

Using Existing User Actions in a User Action Library

With slight modifications, existing user action code can be used in a user action library. You need to include gciua.hf instead of gci.hf or gcirtl.hf. Define a GciUserActionShutdown, and a GciUserActionInit, if it is not already present. Compile, link, and install according to the instructions for user action libraries.

Using Third-party C Code with a User Action Library

Third-party C code has to reside in the same process as the C user action code. Link the third-party code into the user action library itself, and then you can call that code. It doesn’t matter where you call it from.

3.  Loading User Actions

GemBuilder does not support the loading of any default user action library. Applications and Gems must include code that specifically loads the libraries they require.

Loading User Action Libraries At Run Time

Dynamic run-time loading of user action libraries requires some planning to avoid name conflicts. If an executable tries to load a library with the same name as a library that has already been loaded, the operation fails.

When user actions are installed in a process, they are given a name by which GemBuilder refers to them. These names must be unique. If a user action that was already loaded has the same name as one of the user actions in the library the executable is attempting to load, the load operation fails. On the other hand, if the two libraries contain functions with the same implementation but different names, the operation succeeds.

Application User Actions

If the application is to load a user action library, implement an application feature to load it. The GemStone interfaces provide a way to load user actions from your application.

  • GemBuilder for C applications: the GciLoadUserActionLibrary call
  • Topaz applications: the loadua command

The application must load application user actions after it initializes GemBuilder (GciInit) and before the user logs into GemStone (GciLogin). If the application attempts to install user actions after logging in, an error is returned.

Session User Actions

A linked or RPC Gem process can install and execute its own user action libraries. To cause the Gem to do this, use the System class>>loadUserActionLibrary: method in your GemStone Smalltalk application code. A session user action library stays loaded until the session logs out.

The session must load its user actions after the user logs into GemStone (GciLogin). At that time, any application user actions are already loaded. If a session tries to load a library that the application has already defined, it gets an error. The loading code can be written to handle the error appropriately. Two sessions can load the same user action library without conflict.

Specifying the User Action Library

When writing scripts or committing to the database, you can specify the user action library as a full path or a simple base name; it is recommended to use the base name. The code that GemBuilder uses to load a user action library expands the base name <ua> to a valid shared library name for the current platform. For example, the base name custreport would be expanded to libcustreport.so on many UNIX platforms.

The code for loading searches for the file in the following places in the specified order:

  • The current directory of the application or Gem.
  • The directory the executable is in, if it can be determined.
  • The $GEMSTONE/ualib directory.
  • The normal operating system search, as described in Searching for the Library.

Creating User Actions in Your C Application

Loading user action libraries at run time is the preferred behavior for GemBuilder applications. For application user actions, however, you have the option to create the user actions directly in your C application, not as part of a library. When you implement user actions this way, include gcirtl.hf or gci.hf in your C source code, instead of gciua.hf. (Your C source code should include exactly one of these three include files.)

The GciUserActionInit and GciUserActionShutdown functions are not required, but the application must call GciDeclareAction once for each function in the set of user actions.

After your application has successfully logged in to GemStone (via GciLogin), it may not call GciDeclareAction. If your application attempts to install user actions after logging in, an error will be returned.

Verify That Required User Actions Have Been Installed

After logging in to GemStone, your application can test for the presence of specific user actions by sending the following Smalltalk message:

System hasUserAction: aSymbol

This method returns true if your C application has loaded the user action named aSymbol, false otherwise.

For a list of all the currently available user actions, send this message:

System userActionReport

4.  Write the Code That Calls Your User Actions

Once your application or Gem has a way to access the user action library, your GemStone Smalltalk code invokes a user action function by sending a message to the GemStone system. The message can take one of the following forms:

System userAction: aSymbol
System userAction: aSymbol with:arg1 [with:arg2] ...
System userAction: aSymbol withArgs:anArrayOfUpTo8Args

You can use the with keyword from zero to seven times in a message. The aSymbol argument is the name of the user action function, significant to 31 characters. Each method returns the function result.

Notice that these methods allow you to pass up to eight arguments to the C user action function. If you need to pass more than eight objects to a user action, you can create a Collection (for example, an instance of Array), store the objects into the Collection, and then pass the Collection as a single argument object to the C user action function:

| myArray |
myArray := Array new: 10.
"populate myArray, then send the following message"
System userAction: #doSomething with: myArray.

Limit on Circular Calls Among User Actions and Smalltalk

From Smalltalk you can invoke a user action, and within the user action you can do a GciSend, GciPerform, or GciExecute, that may in turn invoke another user action. This kind of circular function calling is limited; no more than 2 user actions may be active at any one time on the current Smalltalk stack. If the limit is exceeded, GemStone raises an error.

5.  Debug the User Action

Even if you intend to use your library only as session user actions, test them first as application user actions with an RPC Gem. As with applications, never debug user actions with linked versions.

Debug your C code in a process that does not include a Gem.
For more information, see Risk of Database Corruption.

Use the instructions for user actions in Chapter 5, “Compiling and Linking” to compile and link the user action library. Then load the user actions from the RPC version of your application or Topaz. To load from Topaz, use the loadua command.

When an error in a user actions is handled by an on:do: block in Smalltalk by default trims the stack on return from the user action. You can set the runtime configuration parameter GemDebuggerActive, to fetch the stack for the error in the user action.

4.4  Executing User Actions

User actions can be executed either in the GemBuilder application (client) process or in a Gem (server) process, or in both.

Choosing Between Session and Application User Actions

The distinction between application user actions that execute in the application and session user actions that execute in the Gem is interesting primarily when the two processes are running remotely, or when the application has more than one Gem process.

Remote Application and Gem Processes

When the application and Gem run on different machines and the Gem calls an application user action, the call is made over the network. Computation is done by the application where the application user action is running, and the result is returned across the network. Using a session user action eliminates this network traffic.

On the other hand, for overall efficiency you also need to consider which machine is more suitable for execution of the user action. For example, assume that your application acquires data from somewhere and wishes to store it in GemStone. You could write a user action to create GemStone objects from the data and then store the objects. It might make more sense to execute the user action in the application process rather than transport the raw data to the Gem.

Alternatively, assume there is a GemStone object that could require processing before the application could use it, like a matrix on which you need to perform a Fast Fourier Transform (FFT). If the Gem runs on a more powerful machine than the client, you may wish to run an FFT user action in the Gem process and send the result to your application.

Applications With Multiple Gems

In most situations, session user actions are preferable, because the Gem does not have to make calls to the application. In the case of a linked application, however, an application user action is just as efficient for the linked Gem, because the Gem and application run as one process. Using an application user action guarantees that if any new sessions are created, they will have access to the same user action functions as the first session.

Every Gem can access its own session user actions and the application user actions loaded by its application. A Gem cannot access another Gem’s session user actions, however, even when the Gems belong to the same application.

Although a linked application and its first Gem run in the same process, that process can have session and application user actions, as in Figure 4.1. Application user actions, loaded by the application’s loading function, are accessible to all the Gems. Session user actions in the same process, loaded by the System class>>loadUserActionLibrary: method, are not accessible to the RPC Gem. Conversely, the RPC Gem’s user actions are not accessible to the linked Gem.

Figure 4.1   Access to Application and Session User Actions

The following sections discuss the various possible configurations in detail.

Running User Actions with Applications

User actions can be executed in the user application process under two configurations of GemStone processes. The configurations differ depending upon whether the application is linked or RPC.

With an RPC Application

In an RPC configuration, the application runs in a separate process from any Gem. Each time the application calls a GemBuilder C function, the function uses remote procedure calls to communicate with a Gem. The remote procedure calls are used whether the Gem is running on the same machine as the application, or on another machine across the network.

The user actions run in the same process as the application. If they call GemBuilder functions, those functions also use remote procedure calls to communicate with the Gem.

In this configuration, all your code executes as a GemStone client (on the application side). It can thus execute on any GemStone client platform; it is not restricted to GemStone server platforms. Care should be taken in coding to minimize remote procedure call overhead and to avoid excessive transportation of GemStone data across the network. The following list enumerates some of the conditions in which you may find occasion to use this configuration:

  • The application and/or the user action needs to be debugged or tested.
  • The user action depends on facilities or implement capabilities for the application environment. Screen management, GUI operations, and control of specialized hardware are possibilities.
  • The application acquires data from somewhere and wishes to store it in GemStone. The user action creates the requisite GemStone objects from the data and then commits them to the repository.

You can run RPC Topaz as the C application in this configuration for debugging to perform unit testing of user action libraries. Apply a source-level debugger to the Topaz executable, load the libraries with the Topaz loadua command, then call the user actions directly from GemStone Smalltalk.

With a Linked Application

In a linked configuration, the application, the user actions, and one Gem all run in the same process (on the same machine). All function calls, from the application to GemBuilder and between GemBuilder and the Gem, are resolved by ordinary C-language linkage, not by remote procedure calls.

Since a Gem is required for each GemStone session, the first session uses the (linked) Gem that runs in your application process. This Gem has the advantages that it does not incur the overhead of remote procedure calls, and may not incur as much network traffic. It has the disadvantage that it must run in the same process as the Gem, so that work cannot be distributed between separate client and server processes. Since the application cannot continue processing while the Gem is at work, the non-blocking GemBuilder functions provide no benefit here.

If a linked application user logs in to GemStone more than once, GemStone creates a new RPC Gem process for each new session. If one of these sessions invokes a user action, the user action executes in the same process as the application. If the user action then calls a GemBuilder function, that call is serviced by the linked Gem, not by the Gem from which the user action was invoked.

In this configuration, your code executes only on GemStone server platforms. It cannot execute on client-only platforms because a Gem is part of the same process. The occasions for using this configuration are much the same as those for running user actions with an RPC application, except that you should not use this one for debugging.

Debug your user actions in a process that does not include a Gem. For more information, see Risk of Database Corruption.

Running User Actions with Gems

Just as with applications, there are two forms of Gems: linked and RPC. The linked Gem is embedded in the gcilnk library and is only used with linked applications.

An RPC Gem executes in a separate process that can install and execute its own user actions. The RPC Gem is RPC because it communicates by means of remote procedure calls, through an RPC GemBuilder, with an application in another process.

However, it is also a separate C program. The Gem itself also uses GemBuilder directly, to interact with the database. That is the reason why the RPC Gem is linked with the gcilnk library. The user action in this configuration executes in the same process as the Gem, with the GemBuilder that does not use remote procedure calls.

Debug your user actions in a process that does not include a Gem. For more information, see Risk of Database Corruption.

The following list enumerates some of the conditions in which you may find occasion to use this configuration:

  • You wish to execute the user action from a Smalltalk application using GemBuilder for Smalltalk. This configuration is required for that purpose.
  • You wish the user action to be available to all or many other C applications.
  • The user action is called frequently from GemStone. This configuration eliminates network traffic between GemBuilder and GemStone.
  • The user action makes many calls to GemBuilder. This configuration avoids remote procedure call overhead.
  • You have a GemStone object or objects that you wish to process first, and your application needs the result. The processing may be substantial. Your GemStone server machine may be more powerful than your client machine and could do it more quickly, or it might have specialized software the user action needs. Also, the result might be smaller and could reduce network traffic.

Previous chapter

Next chapter