Advanced C++ API User Guide

Chapter 1

Advanced Persistence

The information in this chapter augments Chapter 3, Programming with Persistent Storage, in the C++ API User Guide. The material is organized in the following manner:

Controlling Address Space Reservation

Address space is the space of all possible virtual memory addresses. Each address designates one byte of actual or potential virtual memory. When ObjectStore maps a page of data into virtual memory (see ObjectStore's Memory Mapping Architecture in the C++ API User Guide), it reserves address space for objects referenced by pointers on the mapped page. By default, ObjectStore unreserves this address space at the end of each transaction.

Address space is always reserved in 64 KB units. When address space is reserved for an object, ObjectStore actually reserves addresses for the entire 64 KB-aligned portion of memory containing the object. This document refers to a 64 KB-aligned portion of memory for which address space is reserved as a reservation chunk or, simply, chunk.

On 32-bit platforms, if the pages used by a transaction refer to reservation chunks whose total size approaches the size of the current session's address space partition, you might have to control address space reservation.

Purpose of Address Space Reservation

Address space is reserved for a reservation chunk in preparation for the possibility of mapping pages in the chunk. If a page has a pointer to a reservation chunk, address space is reserved for the chunk when the page is mapped (unless space is already reserved for the chunk). If such a pointer is dereferenced, the page containing the referent is mapped at the appropriate offset from the location reserved for the chunk.

Controlling Address Space Reservation

As a transaction proceeds, the amount of address space reserved typically increases. As more pages are mapped, more chunks typically are referred to by mapped pages. As more chunks are referred to by mapped pages, more address space is reserved. By default, ObjectStore releases (that is, unreserves) all reserved address space at the end of each transaction.

Under some circumstances, on 32-bit platforms, address space reservation can consume all address space in the course of a single transaction. This might happen (on 32-bit platforms) if the pages used by a transaction refer to chunks whose total size approaches the total size of the application's persistent storage region (or, for applications that use the sessions facility, the total size of the current session address space partition). When all address space is reserved, ObjectStore signals err_address_space_full.

If you think a transaction might exhaust address space, first consider whether the transaction can be broken up into multiple shorter transactions, each of which consumes less address space. If this is not possible, you can control address space reservation in one of the following ways:

Explicit release of address space is discussed in the following section, Releasing Address Space. Soft pointers are discussed in Using ObjectStore Soft Pointers.

Mid-Transaction Address-Space Release Must Be User Controlled

When all address space is reserved, why does ObjectStore not automatically release some, just as it automatically evicts least-recently-used pages when the client cache is full? The answer concerns pointer validity. When pages are released from address space, all encached pointers to those pages become invalid, because the virtual memory locations designated by the pointers can now be reserved for different data. Any automatic eviction scheme makes it difficult or impossible for the program to determine the pointers that have been rendered invalid.

With explicit address-space release, the program, in effect, specifies the pointers to be released, so there is no question about the pointers that have been rendered invalid and the program can take care not to use the pointers again. If the program subsequently needs pointers to the same objects, it knows to re-retrieve the pointers from the database.

Releasing Address Space

Use explicit release of address space to prevent reservation of all address space or to handle err_address_space_full (see Controlling Address Space Reservation). By default, all reserved address space is released automatically at the end of each transaction. You can release address space explicitly at other times as well, using one of the following:

The class os_address_space_marker provides functions for

To mark a point in the flow of execution of a program, create an instance of os_address_space_marker on the transient heap or the stack. The os_address_space_marker constructor has no arguments.

      os_address_space_marker the_marker;
Call the member function release() on a specified marker to release all address space reserved since the creation of the marker (except address space retained by calls to os_address_space_marker::retain(), later in this section, and any retained by os_retain_address objects. See Retaining the Validity of a Specified Variable). The function has no arguments and no return value.

      the_marker.release();
Caution
Calling this function invalidates all pointers to the objects for which the released address space was reserved.

You can call release() on a marker repeatedly. After the first time you release a marker, releasing the marker releases the address space reserved since the last time the marker was released (except those retained by retain() or os_retain_address).

Example

Suppose that an application must process three large vectors of pointers in a single transaction and that the pointers point to objects scattered among many different reservation chunks. Further suppose that after an element of the vector has been processed it is not used again in the transaction. To avoid exhausting address space, the application could release address space at various points during the traversal of the vector.

In the worst case, each vector element points to a different reservation chunk. In this case, the application should release address space after each RES_LIMIT element has been processed. RES_LIMIT is the size of the persistent storage region (or the current session's address space partition, for multisession applications) divided by 64 KB (minus the number of chunks occupied by addresses reserved before the marker and minus a few chunks for ObjectStore internal data).

If there are an average of 100 referents of the vector elements in each reservation chunk, the application can process 100 times as many elements before releasing address space:

OS_BEGIN_TXN(tx1, 0, os_transaction::update)
{
      Foo *p = ...; // needed across release() calls
      os_address_space_marker the_marker;
      Foo *vec1[LARGE_NUMBER] = ...;
      for (int i = 0; i < vec_size; i++) {
            if ( (i-1) % RES_LIMIT == 0 )
                  the_marker.release();
            process (vec1[i], p);
      }
}
OS_END_TXN(tx1)

Nesting Markers

Address space markers can be nested, which means that several markers can be in effect at the same time. Calling os_address_space_marker::release() on a marker releases the marker and all markers nested within it.

Retaining the Validity of Specified Pointers

The static function os_address_space_marker::retain() allows you to specify exceptions to the release of address space for pointers whose validity you want maintained across release boundaries.

static void retain(
      void *p, 
      os_address_space_marker *marker = 0
);
If marker is 0, the function retains the validity of p across calls to os_address_space_marker::release() (assuming that p is currently valid).

If marker is nonzero, the argument specifies a marker that you can use to release the address space occupied by p's referent. In other words, the function retains the validity of p across calls to os_address_space_marker::release(), except calls to marker.release().

Caution
marker must not be nested within p's associated marker, where a pointer's associated marker is the most deeply nested marker in existence when address space was reserved for the pointer's referent. If marker is nested within p's associated marker, the call to retain() is ignored.

Deleting Markers

Deleting a marker does not release the address space that marker has marked. If a marker is deleted and no call to os_address_space_marker::release() is made, the marker is removed and the address space that it controlled is now controlled by its previous marker. If there is no previous marker, the address space is not governed by any marker and is no longer incrementally releasable.

After the outermost marker is created, no more than 230 minus 1 additional markers can be created before the outermost marker is deleted. This is true even if some or all of the inner markers are deleted.

Using Other Marker Functions

The class os_address_space_marker defines member functions for the following:

See the C++ API Reference for more information.

Using ObjectStore Soft Pointers

ObjectStore soft pointers provide an alternative to using regular pointers to persistent memory. Soft pointers reduce address space usage by deferring the reservation of address space for the soft pointer's referent until the soft pointer is dereferenced. See Controlling Address Space Reservation.

You can also use soft pointers to allow 32-bit applications to access persistent pointers stored by 64-bit applications, as well as to allow 64-bit applications to access pointers stored by 32-bit applications.

Dereferencing a soft pointer is somewhat slower than dereferencing a regular hard pointer, but the soft pointer facility uses a caching mechanism to increase soft-pointer efficiency.

Softness Is Application Relative

In a database, a soft pointer has the same format as a hard pointer. When one application stores a soft pointer in a database, another application subsequently can use the pointer as a regular hard pointer. Inversely, when one application stores a hard pointer in a database, another application can subsequently use the pointer as a soft pointer.

Specifying the Pointers That Are Soft

An application's (or DLL's) schema specifies those pointers that are treated as soft by the application (or DLL). For any pointer-valued data member in a database schema the application's schema can specify the member's value type as a soft pointer class instead of a pointer type.

For example, suppose a database schema contains the class

      class part 
      {
            employee* responsible_engineer;
            . . . 
      };
If your application accesses this database and you want the application to treat the values of part::responsible_engineer as soft pointers, define the application's class part in the following way:

      class part 
      {
            os_soft_pointer<employee> responsible_engineer;
            . . . 
      };
These definitions of the class part are schema compatible. So although the application's definition of part and the database schema's definition of part differ, the application can access the database. When the application accesses an instance of part, it does not initially reserve address space for the part's responsible engineer (an instance of employee).

Because an application's schema specifies those pointers that are to be treated as soft, only values of data members can be treated as soft pointers. Top-level pointers or pointers in top-level arrays cannot be treated as soft.

To defer address-space reservation for referents of top-level pointers, replace the top-level pointers with instances of os_Reference. This class has an API just like the soft pointer API (see Soft Pointer API, following).

Soft Pointer API

Following is a brief description of the member functions provided by the soft pointer classes. Note that the referent of a hard or soft pointer is the object to which the pointer points or refers.

Constructors
The soft pointer classes provide constructors for

Assignment operators
Overloadings of the assignment operator (operator =()) allow for modifying a soft pointer so that it has the same referent as a given hard or soft pointer.

Resolution functions
Before you dereference a soft pointer, you must resolve it, that is, retrieve a hard pointer with the same referent. Resolution is either implicit or explicit. Implicit resolution is supported by the dereference operator (operator ->()) and by a conversion operator, for conversion to a pointer to the referent type. Explicit resolution is supported by the member function resolve(). All these functions return a hard pointer with the same referent as this.

Other functions
The soft pointer classes also provide functions for comparing soft pointers with hard or soft pointers, as well as functions hashing soft pointers and for dumping soft pointers to ASCII and loading soft pointers from ASCII. See the C++ API Reference.

Do not use the C++ memcpy() function or any means other than the soft pointer API to copy soft pointers. Using direct memory manipulation can cause the resulting copy to be unusable by ObjectStore.

Example

Because of the constructors, assignment operators, conversion operator, and operator ->() defined by the soft pointer classes, you often can use soft pointers just as you would use hard pointers. Following is an example of using a soft pointer, an instance of os_soft_pointer:

      #include <ostore/ostore.hh>
      #include <stdio.h>
      class employee 
      {
            static os_typespec *get_os_typespec();
            . . . 
      };
      class part 
      {
            static os_typespec *get_os_typespec();
            . . . 
            os_soft_pointer<employee> responsible_engineer;
            . . . 
      };
      void f() {
            OS_ESTABLISH_FAULT_HANDLER
            objectstore::initialize();
            static os_database *db1 = os_database::open("/thx/parts");
            OS_BEGIN_TXN(tx1, 0, os_transaction::update)
                  part *a_part = new(db1, part::get_os_typespec()) part;
                  employee *an_emp = 
                        new(db1, employee::get_os_typespec()) employee;
                  a_part->responsible_engineer = an_emp;
                  printf("%d\n", a_part->responsible_engineer->emp_id);
            OS_END_TXN(tx1)
            db1->close();
            OS_END_FAULT_HANDLER
      }
The member responsible_engineer is declared to be of type os_soft_pointer<employee>. The class name in angle brackets is the referent type. It indicates that values of responsible_engineer are soft pointers to instances of the class employee.

When a part is created,

      part *a_part = new(db1, part::get_os_typespec()) part;
C++ implicitly calls the no-argument os_soft_pointer constructor, which constructs a null soft pointer as the value of the data member responsible_engineer.

When an employee* (an_emp) is assigned to the member responsible_engineer,

      a_part->responsible_engineer = an_emp;
C++ invokes the soft pointer assignment operator, which makes the soft pointer point to the same object as an_emp.

When the value of responsible_engineer is dereferenced,

      printf("%d\n", a_part->responsible_engineer->emp_id);
C++ invokes the soft pointer -> operator, which resolves the soft pointer. C++ reapplies the -> operator to the resulting hard pointer.

Note that implicit resolution is performed only by the -> and conversion operators. Other operators, such as [] and ++, do not perform implicit resolution and are not defined by the soft pointer classes.

Soft Pointer Classes and Macros

ObjectStore provides several soft pointer classes, but typically your code should refer only to instantiations of the class template os_soft_pointer<T>, such as os_soft_pointer<employee> in the preceding example. The template parameter is the referent type - the type of object referred to by the soft pointer.

Macro
os_soft_pointer is a macro, defined as os_soft_pointer32 on 32-bit platforms and defined as os_soft_pointer64 on 64-bit platforms. When you use os_soft_pointer<T>, therefore, you are actually using os_soft_pointer32<T> or os_soft_pointer64<T>.

Class templates
os_soft_pointer32<T> and os_soft_pointer64<T> define constructors, assignment operators, a conversion operator, operator ->(), and resolve(). Other soft pointer operations are inherited from os_soft_pointer32_base or os_soft_pointer64_base.

Base classes
os_soft_pointer32_base and os_soft_pointer64_base define comparison operators, and decache, hash, dump, and load functions.

Nonclass referent types
If you use a soft pointer type whose referent type is not a class, you must call either the macro OS_SOFT_POINTER32_NOCLASS() or OS_SOFT_POINTER64_NOCLASS(), passing the referent type. This provides a definition for the template instantiation. For example, the following defines the class os_soft_ptr32<float>:

      OS_SOFT_POINTER32_NOCLASS(float);
Implicit instantiation of the template defines operator ->(), which is invalid for nonclass parameters. Calling the macro defines an instantiation that does not provide operator ->().

For soft pointers whose referent type is not a class, you resolve the pointer by calling resolve() or by relying on the conversion operator.

The ObjectStore API explicitly provides the following instantiations of os_soft_pointer32<T> and os_soft_pointer64<T>, using OS_SOFT_POINTER32_NOCLASS() and OS_SOFT_POINTER64_NOCLASS():

Opening a Database Automatically

If a soft pointer refers to an object in a database that is not open, ObjectStore opens the database automatically when the object is accessed (unless the autoopen mode is auto_open_disable). The mode in which the database is opened is determined by the value of objectstore::get_auto_open_mode().

Retaining the Validity of a Specified Variable

When a pointer to persistent memory is assigned to a transiently allocated variable, the value of the variable is valid only until the end of the top-level transaction in which the assignment was made (but see Retaining and Releasing the Validity of All Pointers). You can cause such a variable's value to remain valid across top-level transaction boundaries with the class os_retain_address.

To cause a given variable's value to remain valid, create a stack-allocated instance of os_retain_address, passing to the constructor the address of the given variable:

document *the_doc_p = 0;
os_retain_address( (void**) &the_doc_p);
...
the_doc_p = (document*) (
            db1->find_root("documents_root")->get_value()
      );
As long as the associated instance of os_retain_address exists, ObjectStore retains the validity of the address ithat s currently or subsequently assigned to the variable. At the end of each top-level transaction, ObjectStore retains the validity of the value of the variable rather than unmapping it.

The instance of os_retain_address must be stack allocated; therefore, it is deleted automatically at the end of the block in which it was created. Because lexical transactions establish their own blocks, you do not typically call the os_retain_address constructor within a lexical transaction. The resulting instance would be deleted at the end of the transaction and would, therefore, be of no use in retaining validity across top-level transaction boundaries. (You could, however, use it to retain validity across calls to objectstore::release_persistent_addresses().)

Implementation note
Retaining the validity of a given address prevents release of a portion of address space at least 64 KB in size. See Controlling Address Space Reservation.

os_retain_address defines members for determining the address of an instance's associated variable and for changing an instance's associated variable. See os_retain_address in the C++ API Reference.

Example

Consider a pointer to a database entry point. Using an entry point typically involves looking up a root and retrieving its value, a pointer to the entry point. Frequently this pointer is assigned to a transiently allocated variable for future use. However, its use is limited, because it typically does not remain valid in subsequent transactions.

Example of loss of pointer validity across transactions
      #include <ostore/ostore.hh>
      #include "part.hh"
      void f() {
            OS_ESTABLISH_FAULT_HANDLER
            objectstore::initialize();
            static os_typespec part_type("part");
            part *a_part_p = 0; 
            employee *an_emp_p = 0;
            os_database *db1 = os_database::open("/thx/parts");
            OS_BEGIN_TXN(tx1,0,os_transaction::update)
                  a_part_p = (part*) (
                              db1->find_root("part_root")->get_value()
                  ); /* retrieval */
                  . . .
            OS_END_TXN(tx1)
            OS_BEGIN_TXN(tx2,0,os_transaction::update)
                  an_emp_p = a_part_p->responsible_engineer; /* INVALID! */
                  . . .
            OS_END_TXN(tx2)
            db1->close();
            OS_END_FAULT_HANDLER

}
Example of re-retrieving pointers in subsequent transactions
One way to ensure that the pointer remains valid is to re-retrieve the pointer in each subsequent transaction in which it is required.

      #include <ostore/ostore.hh>
      #include "part.hh"
      f() {
            OS_ESTABLISH_FAULT_HANDLER
            objectstore::initialize();
            static os_typespec part_type("part");
            part *a_part_p = 0; 
            employee *an_emp_p = 0;
            os_database *db1 = os_database::open("/thx/parts");
            OS_BEGIN_TXN(tx1,0,os_transaction::update)
                  a_part_p = (part*) (
                        db1->find_root("part_root")->get_value()
                  ); /* retrieval */
                  . . .
            OS_END_TXN(tx1)
            OS_BEGIN_TXN(tx2,0,os_transaction::update)
                  a_part_p = (part*) (
                        db1->find_root("part_root")->get_value()
                  ); /* re-retrieval */
                  an_emp_p = a_part_p->responsible_engineer; /* valid */
                  . . .
            OS_END_TXN(tx2)
            db1->close();
            OS_END_FAULT_HANDLER
      }
Example of using os_retain_address
A convenient alternative is to use os_retain_address:

      #include <ostore/ostore.hh>
      #include "part.hh"
      void f() {
            OS_ESTABLISH_FAULT_HANDLER
            objectstore::initialize();
            part *a_part_p = 0; 
            os_retain_address( (void**) &a_part_p);
            employee *an_emp_p = 0;
            os_database *db1 = os_database::open("/thx/parts");
            OS_BEGIN_TXN(tx1,0,os_transaction::update)
                  a_part_p = (part*) (
                              db1->find_root("part_root")->get_value()
                  ); /* retrieval */
                  
                  . . .
            OS_END_TXN(tx1)
            OS_BEGIN_TXN(tx2,0,os_transaction::update)
                  an_emp_p = a_part_p->responsible_engineer; /* valid */
                  . . .
            OS_END_TXN(tx2)
            db1->close();
            OS_END_FAULT_HANDLER
      }
Note that although you can use a_part_p from one transaction to the next without re-retrieving its value, you cannot use it between transactions. As always, you must be within a transaction to access persistent data.

Retaining and Releasing the Validity of All Pointers

To convert an existing application to use ObjectStore, it might be inconvenient to rewrite the code to re-retrieve pointers to persistent memory in each transaction or to use os_retain_address (see Retaining the Validity of a Specified Variable). A feature of ObjectStore enables you to retain the validity of all persistent addresses across transaction boundaries, so that it is unnecessary to re-retrieve them or to use os_retain_address on a case-by-case basis.

The advantage of this feature is that code is easier to port to ObjectStore. The disadvantage is that ObjectStore might run out of address space if too much persistent data is referenced. This is because ObjectStore normally releases all reserved addresses at the end of each transaction and the use of this feature disables that release, which might leave less address space available for reservation in subsequent transactions. (See Controlling Address Space Reservation.) In addition, database access might be somewhat slower, particularly if multiple databases are referenced.

The static member function objectstore::retain_persistent_addresses() globally enables retaining persistent addresses. It has no arguments.

Releasing address space
The static member function objectstore::release_persistent_addresses() globally releases persistent addresses. After this function is called, all existing transient-to-persistent pointers are invalidated, except possibly those for which there is a corresponding instance of os_retain_address (see Retaining the Validity of a Specified Variable). If the argument to release_persistent_addresses() is 0 (the default), pointers for which there is a corresponding instance of os_retain_address are retained. If the argument to release_persistent_addresses() is nonzero, pointers for which there is a corresponding instance of os_retain_address are not retained.

You can determine whether persistent addresses are currently retained with objectstore::get_retain_persistent_addresses(), which returns nonzero for true and 0 for false.

Example: retaining persistent addresses
Following is an example of retaining addresses:

      #include <ostore/ostore.hh>
      #include "part.hh"
      f() {
            OS_ESTABLISH_FAULT_HANDLER
            objectstore::initialize();
            os_database *db1;
                  . . .
            person *p, *q;
                  . . .
            objectstore::retain_persistent_addresses();
            OS_BEGIN_TXN(tx1,0,os_transaction::update)
                  p = (person *) (db1->find_root("fred")->get_value());
                  /* Now p is valid, and can be referenced normally. */
                  p->print_info();
            OS_END_TXN(tx1)
            /* p cannot be dereferenced outside a transaction, but it */
            /* can be stored anywhere. */
            q = p;
            OS_BEGIN_TXN(tx1,0,os_transaction::update)
                  /* If persistent addresses were not retained, we */
                  /* could not do this without using references for p and q */
                  q->print_info();
            OS_END_TXN(tx1)
            OS_ESTABLISH_FAULT_HANDLER
      }

Setting Data Fetch Policies

An ObjectStore application can control, for each cluster, the granularity of data transfers from the Server to the client. When an application dereferences a pointer to an object that is not already resident in the client cache, ObjectStore retrieves from the Server at least the page containing the object. The default behavior is to retrieve only the page containing the object. However, in some circumstances, retrieving additional pages can improve performance if the objects stored nearby in the database are likely to be referenced within a brief period of time.

Differences in granularity between fetch policies
ObjectStore has several fetch policies you can associate with a given cluster to control transfer granularity. Each fetch policy specifies the transfer granularity used when an object in the cluster needs to be transferred:

Specifying a fetch policy
You specify the fetch policy for clusters, segments, or databases using the member function set_fetch_policy(), declared as follows:

enum os_fetch_policy { 
      os_fetch_page, 
      os_fetch_cluster,
      os_fetch_segment, 
      os_fetch_stream
}; 
static void set_fetch_policy( 
      os_fetch_policy policy,
      os_int32 bytes
); 
os_cluster::set_fetch_policy() sets the fetch policy for a specified cluster. os_segment::set_fetch_policy() sets the fetch policy for all clusters in a specified segment, including clusters created by the current process in the future. Similarly, os_database::set_fetch_policy() sets the fetch policy for all segments in a specified database. Finally, objectstore::set_fetch_policy() sets the fetch policy for all databases retrieved by the current process.

Note that a fetch policy established with set_fetch_policy() (for either a segment or a database) remains in effect only until the end of the process making the function call. Moreover, set_fetch_policy() affects only transfers made by this process. Other concurrent processes can use a different fetch policy for the same segment or database.

os_fetch_segment Policy

For applications that manipulate substantial portions of small segments, the os_fetch_segment policy is appropriate. Under this policy, ObjectStore attempts to fetch the entire segment containing the desired page in a single client/server interaction, if the segment fits in the client cache without evicting any other data. If there is not enough space in the cache to hold the entire segment, the specified number of bytes is fetched, rounded up to the nearest positive number of pages. (Note that if you specify 0 bytes, this is rounded up and the unit of transfer is a single page.) The os_fetch_segment policy is very efficient if a significant portion of the segment is required, but it wastes time and bandwidth if only a few pages are to be referenced.

os_fetch_cluster Policy

Use the os_fetch_cluster policy when performing an operation that manipulates a substantial portion of a small cluster. Under this policy, ObjectStore attempts to fetch the entire cluster containing the desired page in a single client/Server interaction if the cluster fits in the client cache without evicting any other data. If there is not enough space in the cache to hold the entire cluster, the behavior is the same as for os_fetch_page, with a fetch quantum specified by bytes.

os_fetch_page Policy

If your database contains clusters larger than the client cache of your workstation or if your application does not refer to a significant portion of each cluster in the database, you should use the os_fetch_page fetch policy. This policy causes ObjectStore to fetch a specified number of bytes at a time (rounded up to the nearest positive number of pages), beginning with the page required to resolve a given object reference. Appropriate values for the fetch quantum might range from 4 KB to 256 KB or higher, depending on the size and locality of the application data structures.

      os_cluster *text_cluster; 
      /* The text cluster contains long strings of characters */
      /* representing page contents, which tend to be referred */
      /* to consecutively. So tell ObjectStore to fetch them */
      /* 16 KB at a time. */ 
      text_cluster->set_fetch_policy (os_fetch_page, 16384);

os_fetch_stream Policy

For special applications that scan sequentially through very large data structures, os_fetch_stream might considerably improve performance. As with os_fetch_page, os_fetch_stream enables you to specify the amount of data to fetch in each client/server interaction for a particular cluster. In addition, it specifies that a double buffering policy should be used to stream data from the cluster.

This means that when you scan a cluster sequentially, after the first two transfers from the cluster, each transfer from the cluster replaces the data cached by the second-to-last transfer from that cluster. This way, the last two chunks of data retrieved from the cluster generally are in the client cache at the same time. And after the first two transfers, transfers from the cluster generally do not result in eviction of data from other clusters. This policy also greatly reduces the internal overhead of finding pages to evict.

      os_cluster *image_cluster; 
      /* The image cluster contains scan lines full of pixel data, */
      /* which we're about to traverse in sequence for image */
      /* sharpening. Telling ObjectStore to stream the data from */
      /* the server in 32 KB chunks gives us access to adjacent */
      /* scan lines simultaneously and optimizes client/server traffic. */
      image_cluster->set_fetch_policy (os_fetch_stream, 32768);
When you perform allocation that extends a cluster whose fetch policy is os_fetch_stream, the double buffering described earlier begins when allocation reaches an offset in the cluster that is aligned with the fetch quantum (that is, when the offset mod the fetch quantum is 0).

When the Fetch Quantum Is Too Large

For all policies, if the fetch quantum exceeds the amount of available cache space (cache size minus wired pages), transfers are performed a page at a time. In general, the fetch quantum should be less than half the size of the client cache.

Using Persistent Unions

Whenever you activate a member of a persistent union, including initially, you must make a function call indicating the active member. The function to call is objectstore::set_union_variant(). You can determine the active member of a union with objectstore::get_union_variant().

objectstore::set_union_variant()

static void set_union_variant( 
      void *address, 
      const char *type_name, 
      os_unsigned_int16 variant 
); 
address is the persistent memory address of the union.

type_name is the name of the union type. (This argument is necessary because multiple objects might be collocated at the same address.)

variant is the number that corresponds to the position of the active member (1 for the first, 2 for the second, and so on; 0 indicates an uninitialized union).

An exception occurs if any of the following is true:

objectstore::get_union_variant()

static os_unsigned_int16 get_union_variant( 
      void *address, 
      const char *type_name 
); 
Returns the number that corresponds to the position of the active member (1 for the first, 2 for the second, and so on; 0 indicates an uninitialized union) of the specified union.

address is the persistent memory address of the union.

type_name is the name of the union type. (This argument is necessary because multiple objects might be collocated at the same address.)

Union Overhead

Each union is associated with 16 bits of extra overhead in the form of internal schema information (minus savings due to optimization of certain cases involving union nesting). The use of unions also disables certain schema information storage optimizations. Consider benchmarking your application to determine if the actual memory savings outweigh the additional computational overhead.

Example

The following example shows the definition of a class with a union-valued member. set_union_variant() is called in the set functions for the member.

/*** example.cc ***/
#include "example.hh"
os_int32 main() {
OS_ESTABLISH_FAULT_HANDLER
      objectstore::initialize();
      os_database *db = os_database::create("example.db", 0666, 1);
      os_transaction::begin(os_transaction::update);
      kgraph *p_kg1 = new(db, kgraph::get_os_typespec()) kgraph();
      kgraph *p_kg2 = new(db, kgraph::get_os_typespec()) kgraph();
      if (!p_kg1->verify() || !p_kg2->verify()) return -1;
      p_kg1->set_int(5);
      if (!p_kg1->verify()) return -1;
      p_kg2->set_kg(p_kg1);
      if (!p_kg2->verify()) return -1;
      os_transaction::abort();
      db->destroy();
      return 0;
OS_END_FAULT_HANDLER
}
/*** example.hh ***/
#include <iostream.h>
#include <ostore/ostore.hh>
class kgraph { 
private:
      union kg_union { 
            kgraph* kg_kg; 
            os_int32 kg_int; 
      } kg; 
      os_int16 kg_type; 
public:
      kgraph()
      {
            kg_type = 0;
      }
      
      void set_kg( kgraph* new_kg) 
      { 
            kg_type = 1; 
            if (objectstore::is_persistent(this)) 
                  objectstore::set_union_variant( &this->kg, "kg_union", 1 ); 
            kg.kg_kg = new_kg;
      } 
      void set_int( int new_int ) 
      { 
            kg_type = 2; 
            if (objectstore::is_persistent(this)) 
                  objectstore::set_union_variant( &this->kg, "kg_union", 2 ); 
            kg.kg_int = new_int;
      } 
      os_boolean verify()
      {
            os_int16 uv = 
                  objectstore::get_union_variant( &this->kg, "kg_union");
            if (kg_type != uv)
                  cout << "union variant verification failed: expected "
                        << kg_type << " but returned " << uv << endl;
            return (kg_type == uv);
      }
      static os_typespec *get_os_typespec();
};
/*** schema.cc ***/
#include <ostore/ostore.hh>
#include <ostore/manschem.hh>
#include "example.hh"
OS_MARK_SCHEMA_TYPE(kgraph);
Whenever either kgraph::set_kg() or kgraph::set_int() is called, the embedded call to objectstore::set_union_variant() records the currently active member object.

Setting Segment Reference Policies

Every segment has one of the two following reference policies:

(These reorganization tools are provided in the current release or will be provided in subsequent releases.)

In addition to affecting reorganization, a segment's reference policy also affects garbage collection. The ObjectStore garbage collector automatically treats exported objects in export_id_access_required segments as garbage-collection roots. For garbage collection of dsco_access_allowed segments, you must identify the roots (the objects referred to by other segments) explicitly.

Setting and Getting Segment Reference Policies

To allow efficient operation of reorganization tools on a given segment, do both of the following:

You specify a segment's reference policy when you create the segment.

enum objectstore::segment_reference_policy { 
      export_id_access_required,
      dsco_access_allowed 
};
os_segment* os_database::create_segment(
      objectstore::segment_reference_policy reference_policy
);
dsco_access_allowed refers to regular, nonexport ID access.

If you fail to export an object in an export_id_access_required segment, cross-segment pointers to the object are treated as illegal pointers (see objectstore::set_null_illegal_pointers() in the C++ API Reference).

You can get a segment's reference policy with the following function:

objectstore::segment_reference_policy 
      os_segment::get_segment_reference_policy() const;
You can specify the default reference policy for newly created segments in a given database with the following:

void 
os_database::set_new_segment_reference_policy(
objectstore::segment_reference_policy reference_policy_default);
err_no_trans is signaled if there is no transaction in progress at the time of the call.

You can also specify a default reference policy when you create a database. See os_database::create() and os_database::open() in the C++ API Reference.

You can get a database's default reference policy with the following:

objectstore::segment_reference_policy 
      os_database::get_new_segment_reference_policy() const;

Exporting Objects

You export an object with objectstore::export():

static os_export_id objectstore::export(
      void const * export_address
);
This function exports the top-level object pointed to by export_address. If export_address does not point to a top-level object, this function exports the top-level object containing the specified address. (An object is a top-level object if it is the entire object allocated by some call to new.)

Exporting an object assigns the object an ID (the object's export ID) that is unique within the object's segment. The export ID is used internally by ObjectStore. export() returns the exported object's export ID, an instance of os_export_id.

The database containing the exported object must be opened at the time of the call to export(). If the specified top-level object is not already exported, the database containing it must be opened for update and the current transaction must be an update transaction. If the object is already exported, calling this function is a read-only operation.

If you retrieve an export ID in a transaction that is later aborted, do not use the export ID after the abort. ObjectStore might reuse the export ID in a subsequent transaction.

export_address is not required to point to the beginning of any object.

err_invalid_export_id is signaled if you dereference a cross-segment pointer to an exported object that has been deleted.

err_database_not_open or err_database_not_found is signaled if you retrieve a cross-segment pointer (or resolve a cross-segment soft pointer) to an exported object in a database that is not open and cannot be autoopened (see objectstore::set_auto_open_mode()).

err_transient_pointer is signaled if export_address is not within the current application's persistent storage region.

err_wrong_session is signaled if export_address is within the current application's persistent storage region but not within the current session's address space partition.

err_not_assigned is signaled if export_address is in the address space partition for the current session but is not currently a valid address (for example, because it was retrieved in a prior transaction).

err_no_such_object is signaled if export_address refers to unallocated persistent storage.

err_export_schema_segment is signaled if export_address points to an object in the system segment of a database.

Examining Export IDs

ObjectStore provides functions for manipulating export IDs in the following ways:

There is also an export ID cursor class that supports traversal of all export IDs in a segment.

See os_export_id and os_export_id_cursor in the C++ API Reference.

Example

Following is an example that illustrates setting segment reference policies, exporting objects, and examining export IDs.

#include <iostream.h>
#include <ostore/ostore.hh>
#include <ostore/mop.hh>
#include "Widget.h"

int main(int argc, char** argv) 
{
OS_ESTABLISH_FAULT_HANDLER
      objectstore::initialize();
      const char* db_name = argv[1];
      os_database* db = create_db(db_name, "widgets");
      show_db(db, "widgets");
      show_all_exports(db, "widgets");
      db->close();
      db->release_pointer();
OS_END_FAULT_HANDLER
}
os_database* create_db(const char* db_name, char* root_name) 
{
      os_database* db = os_database::create(db_name, 0664, 1);
      os_transaction* txn = 
            os_transaction::begin(os_transaction::update);
      // set the default segment reference policy for newly created 
      // segments within the database.
      db->set_new_segment_reference_policy(
            objectstore::export_id_access_required
      );
      // create an export_id_access_required segment (per db-default)
      os_segment* seg1 = db->create_segment(); 
      // create an dsco_access_allowed segment
      os_segment* seg2 = 
            db->create_segment(objectstore::dsco_access_allowed); 
      // create an export_id_access_required segment explicitly
      os_segment* seg3 = 
            db->create_segment(
                  objectstore::export_id_access_required
            ); 
      Widget* widget1 = 
            new (seg1, Widget::get_os_typespec()) Widget("X");
      // make sure that widget1 has an export_id assigned because it 
      // will be the target of a cross-segment hard pointer.
      objectstore::export(widget1);
      Widget* widget2 = 
            new (seg2, Widget::get_os_typespec()) Widget("Y");
      // seg2 doesn't require export-id access so the export isn't 
      // required but it is ok to export locations within the segment 
      // anyway.
      objectstore::export(widget2);
      Widget* widget3 = 
            new (seg3, Widget::get_os_typespec()) Widget("Z");
      Widget** widgets = 
            new (seg3, os_typespec::get_pointer(), 3) Widget*[3];
      widgets[0] = widget1;
      widgets[1] = widget2;
      widgets[2] = widget3;
      // set_value automatically exports the widgets array
      db->create_root(root_name)->set_value(widgets);
      txn->commit();
      delete txn;
      return db;
}
void show_db(os_database* db, char* root_name) 
{
      os_transaction* txn = 
            os_transaction::begin(os_transaction::read_only);
      os_database_root* widget_root = db->find_root(root_name);
      Widget** widgets = (Widget**)widget_root->get_value();
      widget_root->release_pointer();
      for (int i=0; i<3; i++) {
            os_export_id widget_export_id = 
            objectstore::get_export_id(widgets[i]);
            if (widget_export_id.is_null()) {
                  cout << "widget number " << i << " is not exported\n";
            } 
            else {
                  os_segment* widget_segment = 
                        os_segment::of(widgets[i]);
                  cout << "widget number " << i << " is exported in segment "
                        << widget_segment->get_number() 
                              << " with export id value "
                                    << widget_export_id.get_value() << "\n";
                  widget_segment->release_pointer();
            }
      }
      txn->commit();
      delete txn;
}
void show_all_exports(os_database* db, char* root_name) 
{
      os_transaction* txn = 
            os_transaction::begin(os_transaction::read_only);
      os_segment_cursor segs(db);
      os_segment* seg;
      while (seg = segs.next()) {
            os_export_id_cursor eids(seg);
            os_export_id eid;
            while (!(eid = eids.next()).is_null()) {
                  void* addr = seg->resolve_export_id(eid);
                  os_type const* export_type = os_type::type_at(addr);
                  cout << "export id value " << eid.get_value() 
                        << " in segment number " << seg->get_number()
                              << " refers to ";
                  if (export_type) {
                        char* type_string = export_type->get_string();
                        cout << "an object of type " << type_string << "\n";
                        delete[] type_string;
                  } 
                  else {
                        cout << "an object of unknown type\n";
                  }
            }
            seg->release_pointer();
      }
      txn->commit();
      delete txn;
}


[previous] [next]

Copyright © 1999 Object Design, Inc. All rights reserved.

Updated: 03/10/99 12:24:44