Monday, October 28, 2013

Beware the void* trap

I'd like to take a little bit of time today to share a problem that took me numerous days to track down and solve because it was so obscure and stealthy.  At work, we use ZeroMQ to handle any inter-process communication, and our primary language is C++.  I had begun a fairly intense project to remove ZeroMQ from the intra-process communications of a particular daemon, leaving it only for when we need to cross process boundaries.  Basically, once the messages that we want get into our daemon, there is no reason to pass them off to threads and such by serializing them to byte arrays (since that what a ZeroMQ message is) when they could be added (as objects) the lists, passed around to thread pools, etc.

As I was nearing the completion of the project, I found that all outbound communication from one of the sections of code seemed to be lost.  This was strange because the inbound communication was handled properly.  I had refactored both directions, so it was quite possible that I had messed something up.  However, no matter how many times I checked the logic, no matter how many log statements I made at each line, nothing looked amiss.  I struggled for days trying to understand why the "send()" calls seemed to be going nowhere, and the answer shocked me.

Here is a simplistic version of the code that I had.  Basically, this code receives (from somewhere else in the program) a list of messages to send to a ZeroMQ socket as a single atomic message.  This is accomplished by adding the "ZMQ_SNDMORE" ("send more") flag to the call.
void sendMessageToEndpoint1( std::list<zmq::message_t*>& messages ) {
   while( messages.size() > 0 ) {
      //! This is the ZeroMQ message that we're going to send.
      //! It has been prepared for us elsewhere.
      zmq::message_t* message = messages.front();
      // Remove the message from the list.
      // The size of this list is now the number of remaining messages to send.
      message.pop_front();
      
      //! This contains the flags for the "send" operation.  The only flag that
      //! we're actually going to set is whether or not we have more messages
      //! coming, and that's for every message except the last one.
      int flags = messages.size() > 0 ? ZMQ_SNDMORE : 0;
      // Send the message to the endpoint.
      // Note that "endpoint1" is of type "zmq::socket_t*".
      endpoint1->send( message, flags );
      
      // We can now delete the message.
      delete message;
   }
}

I promise you that I had logged something before and after every statement, and everything was exactly as I expected it to be.  No exceptions were thrown.  There were no compiler warnings.  But no client on the other side of "endpoint1" ever got any of the messages that were being sent.  This drove me crazy.

The answer is that I was passing the wrong thing to "zmq::socket_t::send()".  Unlike the "recv()" ("receive") call, which takes a pointer to a "zmq::message_t", the "send()" call merely takes a reference to a "zmq::message_t".  Clearly this is a type error, so the compiler should have caught it.  But it didn't.

Here's the signature of the "send()" function.  Basically, it sends a message with some optional flags.
bool send( zmq::message_t& message, int flags = 0 );

I was sending it a "zmq::message_t*" and "int", so the compiler should have reported an error, since the type of the first argument was incorrect.  However, no error (or warning) was printed, and it compiled fine.  Even stranger, nothing bad happened when I called "send()".  Nothing good happened, either, but the code ran with the only strange symptom being that my "send()" calls seemed to do nothing.  The client on the other side of the ZeroMQ socket simply never received the message.

So, what's up with that?

It turns out that there is another "send()" function, one that takes three parameters.  It sends an arbitrary number of bytes with some optional flags.
size_t send( void* buffer, size_t length, int flags = 0 );

And there's the rub.

We've already established that the first "send()" function shouldn't work.  But here's a second "send()" function that does meet our signature.  As for the first parameter, a "zmq::message_t*" will be implicitly cast to "void*" in C++.  As for the second parameter, "int" will be implicitly cast to "size_t", which is just an unsigned integral type.  As for the third parameter, it is not specified, so it'll be set to zero.

This second "send()" is clearly not what I wanted to use, but the compiler doesn't know that I thought that the function required a pointer to a message, not a reference to it.  Since "ZMQ_SNDMORE" is defined to be the number 2, this call to "send()" only attempts to transmit two bytes.  And because a "zmq::message_t" is certainly larger than two bytes (it is actually at least 32 bytes), the data to copy, from the second "send()" function's perspective, is always present.  This means that in addition to not getting any warnings or errors, I am also guaranteed to have this code never crash, since it will always send the first two bytes of the "zmq::message_t" structure.

Naturally, the fix was to send the dereferenced version of the message, and everything worked fine after that.  The moral of the story here is to watch out for implicit "void*" conversion.  And if you are making a library that accepts a byte buffer for reading/writing purposes, please set the type of that buffer to some byte-oriented type, such as "char*" or "uint8_t*".  These would require explicit casts, thus preventing accidental use as in my case.

Sunday, April 21, 2013

Don't let std::stringstream.str().c_str() happen to you

If you're coming to C++ from C, then you will quickly learn to love std::stringstream.  These things let you quickly build out a (possibly huge) string by just tacking on string literals or any other variables to the end.  It's useful for building on-the-fly SQL queries or constructing configuration or connection strings that involve numbers (such as port numbers), since you don't have to pre-define a buffer of known length and snprintf onto the end of it and check for length issues and such.

And you'll love std::string, since that'll save you countless "strdup" calls and null checks.  std::string also has some extra powers that make him way more useful than character buffer manipulation, but still less amazing (and heavy) than std::stringstream.

Anyway, you'll also quickly find that most functions don't accept std::stringstream or std::string; rather, they accept "const char*", which is fine by me.  In fact, std::string has a "c_str" function that will return just such a pointer, and std::stringstream has a "str" function that will return a std::string, so that's great, right?

Yes, absolutely.

But watch out!

But watch out for this:
//! This is our string stream; we're just going to put something
//! in it for fun.  This example will use a made-up connection string.
std::stringstream myStringStream;
// Set up the "connection string"; note for example purposes that
// these could be variables of any type; much like the thing at the
// end is an integer.
myStringStream << "tcp://" << "localhost" << ":" << 9001;

// Create a character pointer so that another function can use it.
const char* myPointer = myStringStream.str().c_str();

// Use that in some function.
someCStyleFunction( myPointer );

Did you see the problem?

When "myPointer" was created, it called "c_str" on a string that was only alive for the duration of that line.  After that line is over, the string that generated the character pointer has been deleted; thus, the pointer to its data is invalid.

Valgrind will complain about this as accessing some memory that was deleted by the destructor of std::string, but you'll probably be too confused to realize what's going on.

In a single-threaded situation, you might be able to slide by without noticing this because nothing has used that memory just yet.  However, in a multi-threaded situation, that memory is essentially instantly whisked up by other threads for other uses.  And now your character pointer points to random other data.  Welcome to what might be hours of troubleshooting and debugging.

The proper solution

Since "c_str" returns a pointer to the internal buffer of a std::string, and since you don't have to free it, it means that the character pointer that it returns is only valid for the lifetime of the std::string that it came from.

Our earlier example could be addressed in one of two ways.

The sneaky way

Don't let the std::string go out of scope by ending the line.  The "str" function's result, a std::string, won't be cleaned up until after "someCStyleFunction" completes, so this gets around the problem.  However, later expansion or debugging of the code might inadvertantly re-introduce it.  Avoid this method.
//! This is our string stream; we're just going to put something
//! in it for fun.  This example will use a made-up connection string.
std::stringstream myStringStream;
// Set up the "connection string"; note for example purposes that
// these could be variables of any type; much like the thing at the
// end is an integer.
myStringStream << "tcp://" << "localhost" << ":" << 9001;

someCStyleFunction( myStringStream.str().c_str() );

The classy way

Actually store the std::string so that it goes out of scope when you want it to.  This makes it clear what the string is for and what its scope is.
//! This is our string stream; we're just going to put something
//! in it for fun.  This example will use a made-up connection string.
std::stringstream myStringStream;
// Set up the "connection string"; note for example purposes that
// these could be variables of any type; much like the thing at the
// end is an integer.
myStringStream << "tcp://" << "localhost" << ":" << 9001;

//! This is the string that we have created with our string stream.
std::string myString = myStringStream.str();

// Create a character pointer so that another function can use it.
const char* myPointer = myString.c_str();

// Use that in some function.
someCStyleFunction( myPointer );

Hopefully this might save you some time.  I spent hours researching the thread-safety of the STL for my current g++ version and was lead down all kinds of wrong paths for a simple, simple scoping issue.

Sunday, February 24, 2013

Why C++11's std::chrono stuff is awesome

C++11 introduced the "std::chrono" namespace, and one of the coolest things about it are the standard "duration" objects.  These ones are already defined:

  • nanoseconds
  • microseconds
  • milliseconds
  • seconds
  • minutes
  • hours
(You can make your own, but that's not the point, here.)

Here are the two important points to take away from this, if nothing else:
  1. All of these are different template definitions of "std::chrono::duration".
  2. All of these may be converted to and from each other (as best as they can).

Real-world example

How often have you seen something like this?
void SomeClient::setTimeout( int timeout );

I've seen this over and over, and it's never easy to figure out what's going on.  Is the timeout in seconds?  Milliseconds?  Microseconds?  None of the above?  There's no easy way to tell; you have to rely on the documentation (if there even is any).

Then you end up with a call like this:
myClient.setTimeout( 500000 );

That doesn't really help.  So, whatever the function expects, this call is sending a lot of that unit of time.  Now it's (1) still dubious as to the units, and (2) hard to read, since there are just a bunch of numbers in a row.  We can't easy know what's going on.

Enter chrono.

Imagine that the function instead looked like this:
void SomeClient::setTimeout( std::chrono::microseconds timeout );

This immediately tells us that the preferred units of measurement are microseconds.  It probably means that any more precise and the function won't care.

So then my call ends up looking like this:
myClient.setTimeout( std::chrono::milliseconds( 500 ) );

Now it's clear that the timeout is being set to 500 milliseconds, and who cares what the function wants.  It doesn't matter.  The units that we pass in will be properly converted.  In this case, they'll be multiplied by 1,000.  Done deal.

What if...?

Finally, what happens if we tried to do it the old way?
myClient.setTimeout( 500000 );

This results in a compile-time error.  Unreadability, you're doin' it wrong.

Tuesday, February 5, 2013

It's 2013; use "getaddrinfo" for sockets

If you're anything like me, you've come to accept this as true:

  • Connecting to anything via a scripting language is easy; if it's a domain name, then it'll be translated.  If there's a port number at the back, then it'll be extracted.  IPv4 or IPv6?  No problem.
  • Doing the same amount of work in Linux using C/C++ is a nightmare.
Well, it turns out that that's not the case.  However, because of the usual man page obfuscation involving networking, it has seemed that way to me for years.

Calling "getaddrinfo"

The function "getaddrinfo" is meant to be the starting place for network connections.  It takes two strings, optional hints, and a pointer to use for your result structure.  Seems simple, right?

Conceptually, you call it in this way:
struct addrinfo* targetAddress = NULL;
int result = getaddrinfo(
   "your-thing.example.com",
   "80",
   NULL,
   &targetAddress
);
if( result != 0 ) {
   cout << "Error resolving address: " << gai_strerror( result ) << endl;
   // ...
}
// ...
freeaddrinfo( targetAddress );

Ultimately, this takes "your-thing.example.com", resolves it to an IP address (it could be IPv4 or IPv6, or both); takes "80", and resolves it to a port number (this could have been "http" and it would still work); and puts the resulting information into "targetAddress", which it allocates for you.

When you're done, free "targetAddress" using "freeaddrinfo".

And yes, this thing has its own error function, "gai_strerror".

Using a "struct addrinfo*"

The result of "getaddrinfo" can be immediately used by "socket":
int socketHandle = socket(
   targetAddress->ai_family,
   targetAddress->ai_socktype,
   targetAddress->ai_protocol
);
if( socketHandle < 0 ) {
   cout << "Could not create socket." << endl;
   // ...
}
int result = connect(
   socketHandle,
   targetAddress->ai_addr,
   targetAddress->ai_addrlen
);
if( result != 0 ) {
   cout << "Could not connect to target address." << endl;
   // ...
}

But don't you normally have to specify the kind of socket?  Is it a stream- or datagram-based?  Is it IPv4 or IPv6?  What network protocol should it use?

That's where things get interesting.  So, a "struct addrinfo*" actually represents a linked list.  "getaddrinfo" then returns a list of possible results.  If we told it only the domain name and port, then it would return many different "struct addrinfo*" instances, all linked together starting from the original one.  To access the next one, use "targetAddress->ai_next".

Now, that might be kind of cool in the sense that you can simply loop through the results and keep trying them until one works.  But you probably know in advance what you're looking for.  I know that I generally only want IP-version-agnostic name translation.

This is where hints come in.

The third parameter to "getaddrinfo" is another "struct addrinfo*", but this time, you get to fill it out, and it'll use the contents of that structure as filter criteria.

For example, if you wanted to connect to "your-thing.example.com:80" over UDP, then you would just set up the hint accordingly:
struct addrinfo hint;
memset( &hint, 0, sizeof( decltype(hint) ) );
// Allow either IPv4 or IPv6.
hint.ai_family = AF_UNSPEC;
// Use a datagram socket (since that's what UDP is).
hint.ai_socktype = SOCK_DGRAM;
// Don't worry about flags.
hint.ai_flags = 0;
// Use UDP, specifically.  UDP is protocol number 17.
hint.ai_protocol = 17;

struct addrinfo* targetAddress = NULL;
int result = getaddrinfo(
   "your-thing.example.com",
   "80",
   &hint, //< Specify the hint here.
   &targetAddress
);

Now, since your protocol information is all filled out already, you're either going to get back something that you can immediately use, or "getaddrinfo" will fail.  Either way, the guesswork is gone.

Monday, September 24, 2012

Net-SNMP and read_all_mibs

For better or for worse, Net-SNMP is the SNMP implementation for use with C/C++ (and Linux).  My feelings on Net-SNMP are a topic for another time, however.

Here, we'll be focused on using the "read_all_mibs" function to load the MIBs that have been deployed and how to interpret the result of that function.

Our major use cases revolve around:

  • Finding information about an OID (known by its numeric form); and
  • Showing the contents of the OID tree (from some arbitrary location).

Initializing Net-SNMP

If your MIBs are in a non-standard directory, or if you just want to take charge and tell Net-SNMP what to do, you'll need to specify the MIB directory before calling "init_snmp".  For example:
netsnmp_set_mib_directory( "/usr/local/snmp/mibs" );

Once you've done that, your next step is to initialize Net-SNMP.  You'll need to give it some arbitrary text (which is used for logging purposes, I believe).  This should normally be the name of your application or something otherwise unique.
init_snmp( "sense-codons" );

"init_snmp" will then load all of the MIBs in the directory that you specified, and it will spew errors all over the screen if your MIBs are in any way not perfect.  And trust me, if you put some proprietary MIBs in that directory, you'll see what I'm talking about.  The point is that the "init_snmp" function might take a minute or two to run; once that function returns, you can rest assured that the MIBs have been loaded and, as best as it could, Net-SNMP has organized them into an OID tree for you to use.

Root nodes

The top-level structure of the OID tree looks like this:
  • ccitt(0)
  • iso(1)
  • joint-iso-ccitt(2)
This much will be understood by Net-SNMP even if there are no MIBs present.

Since this is understood to be a tree, I would have expected there to be an uppermost, root node in the tree, but that's not the case with Net-SNMP.  Rather, all of the top-level items are the roots of their own trees, and there is no root node.

Getting a root node

To obtain the first (that is, lowest-numbered) root node (remember, there are multiple root nodes), you call "read_all_mibs".  This returns a "struct tree*", which has the following useful fields:
  • label, a "char*" that is the name of the node.
  • subid, an unsigned integer that is the numeric ID of the node.
  • next_peer, a "struct tree*" pointer to the next sibling of the node.  This will be NULL if there is no sibling node.
  • child_list, a "struct tree*" pointer to the first child of the node.  This will be NULL if there is no child node.
With a basic set of MIBs, you should get back a node with subid=0, label=ccitt and a sibling of subid=1, label=iso.

Trusting node IDs

This whole setup seems pretty straightforward; you'd think that the usual tree-navigation strategies would work.  However, there is just one more item that I need to mention: node IDs are not technically unique.

Due to the magic and wonder of SNMP, it often is the case that two manufacturers (or even one manufacturer at two times) will redefine a node.  That is, the same node ID will have different names ("label" in the structure).

As far as I can tell, Net-SNMP will always store both names; it will choose one of them to be authoritative, and the others will be more or less shortcuts to the "real" name.  These are stored in the tree as siblings.

So, if you are looking for node ".0.0.8.341.1", under node ".0.0.8.341" there might be multiple "1" nodes.  Once you find the first subid=1 node, you should keep iterating through all of the "next_peer" nodes that have subid=1.  The last such node is the node that you actually one.

For example, assume that you've found the first such node; we'll call it "node".  This little one-line for-loop will update "node" to the last node with that node ID.  This will work even if it is the only such node.
int currentSubid = node->subid;
for( ; node && node->next_peer && node->next_peer->subid == currentSubid; node = node->next_peer );

Wednesday, August 1, 2012

init, setrlimit, and ZeroMQ

Linux has the notion of "resource limits" per process.  This conceptually cool because you could cap potentially dangerous processes with regard to memory size, number of threads, real-time priority, and a bunch of other things (including number of open files, which we'll come to shortly).  Practically, this protects a box from crippling itself under the weight of a malicious (or simply buggy) process.

Resource limits come with two notions of limit: a "hard" limit and a "soft" limit.  The hard limit is the limit set by "the system" and acts as a true upper bound on the particular resource.  The soft limit is a limit that may be changed by the process--but only up until the hard limit.

This way, a process may be able to create a vast number of threads, but it must specifically increase its own limit in order to do so.  It's a tiny little check to make you really think about what you're doing, since you have to manually increase the resource limit within the process.

In the shell: ulimit

When in a shell, the "ulimit" command can be used to change these limits from that moment forward.  This is often used to allow any processes spawned by the shell to have a nonzero (usually infinite) sized core file, which can be used to debug a program that crashes.

In the code: setrlimit

In C/C++, you can use "setrlimit" to change the limits.

How hard is "hard"?

In addition to changing the soft limit, you can change the hard limit if you are root.  Let me say that one more time: you can change hard limits if you are root.

Why is this important?  When the "init" process starts, it sets the hard limit for the maximum number of files open at any one time to 1024.  I ran into this limit years ago, and I tried to change the soft limit, but to no avail.  I couldn't get my process (which must run out of "init") to set this limit higher than 1024, not even as root.  It wasn't until recently that I realized that I didn't try changing the hard limit as root.  I naively thought that a limit documented as "hard" would mean that nothing could change it.  Nope.  One quick call and that number was anything I wanted.

So, why was that important?  We've recently begun to use ZeroMQ in our code at work, and it's pretty cool.  Without getting into too many details, ZeroMQ is a a message-passing system that abstracts communication into "messages" that are sent and received from "sockets".  In theory these sockets have nothing to do with Linux sockets; they are magical mailboxes for magical postmen that deliver magical messages.  In practice, they are super clean wrappers around TCP, UDP, and Unix sockets.

What did I have that needed a thousand network connections?  Nothing.  That's where things got interesting.  ZeroMQ's documentation leads you to believe that you should give up on managing global task lists and mutexes and signaling in favor of its notion of a simple message queue and workers that pull from it.  It wants you to focus on the communication aspect of what you're doing rather than getting lost in mutex hell.  As icing on the cake, ZeroMQ provides "in-process" sockets that don't hit the networking stack at all and can only be used within a process.  Perfect for eliminating task lists and custom thread pool code.

What they don't tell you is that every single ZeroMQ "socket" internally uses the "socketpair" call to generate two real sockets for some internal signalling mechanism--in addition to any networking sockets that are needed.  So, if you really and truly adopted their paradigm, throwing in-process sockets everywhere to simplify all of your code, you would easily run out of file descriptors on a process that spawned from "init".

Another limit?

After allowing my process an unlimited number of open files, I found that ZeroMQ was throwing exceptions about "too many open files", even when I was only around 1,000 open files.  This made no sense at all, since my process limit was now over 65,000.  So what was going on?

It turns out that ZeroMQ internally has a maximum number of files that it wants to respect.  That's great, except that that limit can only be changed in "config.hpp", a random file in the source code of ZeroMQ.  In order to increase that limit to acceptable levels, I had to manually patch ZeroMQ's source code, recompile it, and redistribute it to my equipment.

Summary

  • Resource limits place limits on how much a process may do.
  • "root" can arbitrarily change these limits, including the unchangeable "hard" limits.
  • "nofiles" is the limit for the maximum number of open files.  "no" here represents an abbreviation of the word "number" (for whatever reason that those four extra letters couldn't be trusted).
  • To set the limit in C/C++, use "setrlimit" with RLIMIT_NOFILES.
  • ZeroMQ creates two sockets for every ZeroMQ "socket" that it creates.
  • "max_sockets" is ZeroMQ's own constant (defined in "config.hpp") for the number of sockets that it will allow itself to open.

Tuesday, July 17, 2012

PHP, Objects, and Zvals

PHP, for all its faults, is a powerful and versatile language.  Its syntax is fairly simple for what it allows you to, and the ability to write extensions for it allows it do pretty much anything that it can't do on its own.

However, that being said, there is next to no documentation for writing an extension.  If you want to do something more complicated than the few examples that exist on the Web, well, you're going to be in for hours and hours of pain and suffering.  Just creating an extension that compiles, installs, and doesn't crash PHP is sadly an achievement.

Today, we're going to briefly cover working with PHP "objects" within an extension and memory leaks, the one thing that you certainly do not want to introduce into your scripting language (other than segfaults).

Objects and Arrays

Objects and arrays in PHP behave fairly similarly.  For example, you can "foreach" through the properties of an object just like you can "foreach" through the elements in an [associative] array.  In fact, the functions to get the properties of an object or the elements of an array (from a "zval" pointer) each return a "HashTable" pointer.

To get the propery HashTable for an object, you call:
HashTable* your hash table = Z_OBJPROP_P( your "zval*" here );

For an array, you call:
HashTable* your hash table = Z_ARRVAL_P( your "zval*" here );

Creating Objects and Arrays

For me, one of the main reasons of creating a PHP extension in the first place is to speed up some task that was too slow for PHP on its own (for example, creating large structures from some input).  In particular, I like to create objects for things that should be treated as objects.

Fortunately, creating objects and arrays in PHP is pretty easy (if not pretty).

Your first step is to declare, allocate, and then initialize a new "zval".
zval* shinyNewZval = NULL;
ALLOC_INIT_ZVAL( shinyNewZval );

For an object, call:
object_init( shinyNewZval );

For an array, call:
array_init( shinyNewZval );

Adding Properties

Adding properties (or assciative elements) is straightforward after that.

Here are some functions that do exactly what they say they do.  They add a new property (of a particular type) to the object.
add_property_long( your "zval*" here, property name, integer value );
add_property_double( your "zval*" here, property name, floating-point value );
add_property_bool( your "zval*" here, property name, boolean value );

For example:
add_property_long( shinyNewZval, "accountId", 9001 );
add_property_double( shinyNewZval, "balance", 501.40 );
add_property_bool( shinyNewZval, "isActive", true );

For associative arrays, just replace "property" with "assoc" and you have essentially the same functions, but for arrays.
add_assoc_long( your "zval*" here, property name, integer value );
add_assoc_double( your "zval*" here, property name, floating-point value );
add_assoc_bool( your "zval*" here, property name, boolean value );

For example:
add_assoc_long( shinyNewZval, "accountId", 9001 );
add_assoc_double( shinyNewZval, "balance", 501.40 );
add_assoc_bool( shinyNewZval, "isActive", true );

Now, what if we wanted to add a zval as a property?  For example, we may want to add a zero-indexed array as one of the properties.  Well, here are the two functions to do so:
add_property_zval( your "zval*" here, property name, property "zval*" );
add_assoc_zval( your "zval*" here, property name, property "zval*" );

Logically, the two functions would work just about identically.  One would add a property to an object; the other would do the same to an associative array.

Wrong!

The object version (and only the object version) also increments the reference counter on the "zval".  Why is that important?  When a new "zval" is created, its reference counter is set to "1"; that is, you have a reference to it, since you made it. Which makes perfect sense.  When all references to a "zval" are gone (that is, when the reference counter hits zero), then the "zval" is actually freed, allowing its memory to be reclaimed.

For an integer or something similarly small, this would usually go unnoticed in the short term.  However, for giant structures, that memory can add up fast.  Your script could easily run out of memory and quit, or you could cause the box to start swapping (which may or may not be bad for your platform, but for mine, swapping is considered the onset of death).

So, how do we fix this?  Simple!  Just decrement the reference counter that "add_property_zval" so rudely incremented on its own.  The function for this is "zval_ptr_dtor", where "dtor" is PHP's shorthand for "destructor".  Since the Zend API is in C, you're essentially saying, "Please destroy my 'zval'".
zval_ptr_dtor( address of your "zval*" here );

To conclude our example, here is the code for the object version:
// Note: this will increment the reference count on "someOtherZval".
add_property_zval( shinyNewZval, "transactions", someOtherZval );
// So, we need to decrement that reference count afterward.
zval_ptr_dtor( &someOtherZval );

And here is the code for the associative array version.  Note that the reference count is not incremented by the array version of the function, so we do not need to do anything special here.
add_assoc_zval( shinyNewZval, "transactions", someOtherZval );