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:
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.
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.
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. 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:
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();
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).
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)
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().
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.
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.
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.
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
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.
#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.
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():
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
}
#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
}
#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.
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
}
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_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);
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).
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:
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.)
/*** 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.
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.
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;
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.
See os_export_id and os_export_id_cursor in the C++ API Reference.
#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;
}
Updated: 03/10/99 12:24:44