An introduction to pmemobj (part 3) - types
In all of the previous post the code snippets and examples had persistent pointers (PMEMoid) without any type information - they were simple C structures. Very early in the development of the library we discovered that using something like that was extremely error-prone and generally difficult. That’s why considerable effort was put into encapsulating the PMEMoids with type-safe container. The end result can be compared with how shared_ptr
and the like are done in C++11. All posts after this one will solely use the type-safety features.
Layout declaration
All persistent memory programs that use pmemobj should have a clearly defined memory layout, preferably in its own file. To provide run- and compile- time type-safety the use of special macros is required in addition to declaring structures. For example, a layout for our string storing example would look like this:
POBJ_LAYOUT_BEGIN(string_store);
POBJ_LAYOUT_ROOT(string_store, struct my_root);
POBJ_LAYOUT_END(string_store);
#define MAX_BUF_LEN 10
struct my_root {
char buf[MAX_BUF_LEN];
};
Thanks to this you can now use typed persistent pointers in your code. The string_store
in this code is just a name. When creating or opening a pool with certain layout we recommend using POBJ_LAYOUT_NAME
macro, like so:
pmemobj_create(path, POBJ_LAYOUT_NAME(string_store), PMEMOBJ_MIN_POOL, 0666);
...
pmemobj_open(path, POBJ_LAYOUT_NAME(string_store));
If you find all of this confusing, please read this first - it’s an in-depth explanation of the subject matter.
Typed persistent pointer
Instead of PMEMoids for all of the pointers, you should now use the following construct:
TOID(struct my_root) root;
To dereference this you no longer have to use another variable in conjunction with pmemobj_direct
, a preferred way is to use D_RW
for writing and D_RO
for reading. Like this:
if (D_RO(root)->buf[0] != 0)
D_RW(root)->buf[0] = 0;
Most IDEs correctly evaluate those macros and automatic code completion for types works.
PMEMoid and TOID operations
Generally, two kinds of type-safety macros are distinguished: those that operate on raw PMEMoid
- prefixed with OID_
, and those that operate on typed TOID
- prefixed with TOID_
. All of the pmemobj_
functions take only raw PMEMoids as arguments. We generally recommend using only macros, but if you ever need to ‘cast’ TOID to PMEMoid, you can do it like so:
TOID(struct foo) data;
pmemobj_direct(data.oid);
All of the macros that are not prefixed with either TOID_
or OID_
generally take typed pointers and return them as their result (like the POBJ_ROOT
macro).
Run-time type-safety
Each type in a layout is internally assigned a unique number that can be then used for verification. For instance, an update to existing software may have changed the layout like so:
struct my_root_v1 {
TOID(struct foo) data;
}
struct my_root_v2 {
TOID(struct bar) data;
}
To check whether your version of the layout corresponds with the existing objects, you can use following expression:
if (TOID_VALID(D_RO(root)->data)) {
/* can use the data ptr safely */
} else {
/* declared type doesn't match the object */
}
You can also rely on the embedded type number if you are unsure of the object type, like so:
PMEMoid data;
TOID(struct foo) foo;
TOID(struct bar) bar;
if (OID_INSTANCEOF(data, struct foo)) {
TOID_ASSIGN(foo, data);
} else if (OID_INSTANCEOF(data, struct bar)) {
TOID_ASSIGN(bar, data);
} else {
/* error */
}
Similarities to high-level languages are not accidental.
Example
This is the last time we are going to modify the string store example. The layout.h
modifications can be seen above. First, let’s start with the root object. Instead of first using the pmemobj_root
function and then pmemobj_direct
for the actual pointer, we can use the following line:
TOID(struct my_root) root = POBJ_ROOT(pop, struct my_root);
Remember how I promised that the code will get even shorter? Here you go, writer.c
:
TX_BEGIN(pop) {
TX_MEMCPY(D_RW(root)->buf, buf, strlen(buf));
} TX_END
Because we don’t have the rootp
anymore, this one also becomes simpler, reader.c
:
printf("%s\n", D_RO(root)->buf);
As always, the example is available in the repository.