Hi -- Well, I should be working on mod_fcgid but I wanted to first follow up on a long-ago promise to review the slotmem API by developing a "test harness" framework and a slotmem provider that worked with ZooKeeper as an experiment.
To that end, mod_shmap now offers a simple "RESTful" interface to the slotmem API, and mod_slotmem_zk and mod_zk together provide data storage using ZooKeeper. See http://people.apache.org/~chrisd/projects/shared_map/ and http://people.apache.org/~chrisd/projects/zookeeper/ for the gory details. (Note that the current version of mod_sharedmem in trunk won't fully work with mod_shmap because of the work-in-progress on the inuse flag array. A slightly older version such as r773977 should work OK.) I have a few thoughts and concerns about the slotmem API as it stands. I'll try to divvy them up into sections below. First, some ideas about how to make the existing interface cleaner. Remove mod_slotmem (server/slotmem.c) and the associated ap_slotmem_*() "wrapper" functions. They do nothing that I can see except add an extra layer of complexity. I had no problems using the standard provider interface to the slotmem providers. Why would anyone not just do this? The wrappers also add confusion because the meanings of common terms are changed. For example, providers become "methods" (i.e., "storage methods"); the familiar ap_list_provider_names() becomes ap_slotmem_methods(). But "methods" are what I expect the provider to provide. I suggest removing or renaming away any references to "storage methods" in favour of conventional providers, instances, etc. So, define AP_SLOTMEM_PROVIDER_GROUP as "slotmem" and AP_SLOTMEM_PROVIDER_VERSION as "0". Remove AP_SLOTMEM_STORAGE. Rename mod_sharedmem to mod_slotmem_shm and mod_plainmem to mod_slotmem_plain. Rename ap_slotmem_storage_method to ap_slotmem_provider_t. Rename ap_slotmem_t to ap_slotmem_instance_t. Remove the "slotmem_" prefix from method names (i.e., the names of the functions provided by a provider). If I've got a slotmem provider, it's just extra typing to have to call slotmem->slotmem_create() instead of slotmem->create(). Next, there are several problems I see with the apslotmem_type enum at the moment. For one thing, there's only one enumerated value, SLOTMEM_PERSIST, so there's no way to specify "don't persist". I hacked around that by passing 1 in mod_shmap. Also, if we retain this type, I'd suggest renaming it to a slightly more conventional looking ap_slotmem_type_t. But my personal preference would be to remove it and instead add an unsigned int flags field to ap_slotmem_provider_t, and define some flag values, such as AP_SLOTMEM_FLAG_NOTMPSAFE = 0x0001 (read "not MP-safe") and AP_SLOTMEM_FLAG_PERSIST = 0x0002. While enumerated types are clean if you've got a long list of mutually exclusive values, a flags field follows the existing socache framework, and allows for a number of mutually non-exclusive flags to be set. This is actually closer to what I suspect future development might bring. The mention of the socache API brings me to my next set of points, which proceed from the experience of programming with both APIs and from a strong feeling that the two have a great deal in common. That's where I'm starting from -- specifically a sense that the two APIs should mirror each other closely. (Obviously not everyone might agree with that.) To that end, having a AP_SLOTMEM_FLAG_NOTMPSAFE flag would make the two APIs align on the issue of determining when a caller needs to take special care in a multi-processing context. It would also allow us to remove the global mutex from mod_sharedmem. That would simplify maintenance a good deal. It would also clear up some things I find odd at the moment; for instance, why is there locking in the do() method but not the put() method? (I'm also not 100% sure the locking in the create() method will be very valuable. For example, when APR_USE_SHMGET is true, apr_shm_remove() will call shmctl() to mark the segment for destruction once all processes have detacted from it. So locking around apr_shm_remove() and apr_shm_create() doesn't help the case where another process, which has already attached to the old segment, just keeps on using that old segment after the lock is released.) At any rate, moving responsibility for locking up to the caller level, as the socache API does, I think makes a lot of sense. It means that a caller running in a single-process, single-threaded context can simply choose not to add the overhead of a global lock. Other callers can make their own decisions about what kind of locking they need. Getting to the end now ... next up, a few comments on the provider methods and their arguments. It would be great to pass server_rec* and apr_pool_t* arguments to all the methods (except maybe num_slots() and slot_size()). Some providers may need to report error messages, and to do that they really need a server_rec*. They may also need to allocate data from a temporary pool. The most obvious use case is that r->pool would be passed to a method like get() or put(). I'd also love to see create() split into two methods, create() and init(). The create() method is then responsible for allocating the ap_slotmem_instance_t structure and filling in the arguments; it's also assumed that it will do any argument checks that are required. To this end, the typical use case is to call it in the *first* invocation of the post_config hook, and pass the pconf memory pool. Note that this implies that create() takes two apr_pool_t arguments, one for allocating "permanent" structures which will last the lifetime of the instance, and one for temporary allocations. One would normally pass pconf and ptemp from the post_config hook. The init() method then does all the work of actually setting up the provider's data storage. The typical use case would be to call this from the *second* or subsequent invocations of the post_config hook. This is how the mod_socache_shmcb provider functions and like mod_sharedmem is allocates shared memory segments. I'd suggest removing both the mem() and attach() methods. The mem() method assumes that all providers will return a slab of memory, and that writes to this memory are equivalent to a put(). That's not going to be possible unless all providers deal with memory -- in which case, the only two possible providers that I can see are the "plain" and shared memory providers we have, and then the purpose of a whole API and provider framework is lost on me. The mod_slotmem_zk provider I wrote, for example, just has to return APR_ENOTIMPL for mem() because there's really nothing else it can do. Similarly any provider which uses a DB file or similar backing store isn't going to be able to make mem() available. Better, I think, to drop the method since it's so deeply tied to an in-memory provider model. The attach() method is similarly tied to an in-memory provider model, and in fact, even more tied to the notion of shared memory, I think. From the code in mod_plainmem and mod_sharedmem, moreover, it looks to me like just calling create() will have virtually the same effect as attach() -- if a existing segment is available with the same name, it'll be found and used. If we really need the ability to call create() but have it stop short of actually creating a new segment if no pre-existing one is found, let's add an argument to create() that specifies this behaviour. But my guess would be that there will be few uses for attach() that aren't satisfied by create() as it stands now. The ap_slotmem_callback_fn_t function type should, I think, be modified not to take a void *mem argument in its signature, but instead a slot id (i.e., an unsigned int). Either that or we should remove the do() method. This is again because the existing design assumes that all providers can provide a slab of memory which the function can operate on. (I suppose there is a third option here, in which non-in-memory providers implement do() by allocating a temporary slab of memory and then, for each in-use slot, performing a get(), the callback function, and a put(). This obviously would require an apr_pool_t to be passed to do(), but I'd recommend that anyway.) I'd also suggest putting num_items and item_size elements into the ap_slotmem_provider_t structure, and then we can remove the num_items() and item_size() "getter" methods. That seems equally clean and slightly simpler to me. I'd add a destroy() method that tears down an instance, and expect callers to use it appropriately. That means the caller likely needs to register a cleanup function whenever a successful create() is performed on a slotmem provider. This cleanup function then should call destroy() on the returned slotmem instance. The advantage here is that the providers don't need to register anything, which simplifies their code, and it gives the caller full control over when the slotmem instance should be destroyed. Last but not least on the method front, I'd suggest adding set() and reset() methods. The set() method would work roughly like memset() -- that is, it would accept a byte value and write that into every byte in the slot. The reset() method would do the same as set() with a zero byte value (i.e., blank the memory) but would also "release" the slot. The proposed grab() and return() methods would be unnecessary. A provider which wanted to track which slots were "in use" would simply set the in-use flag when put(), set(), or get() was called, and reset the flag when reset() was called. Finally, just in terms of code simplicity, I noticed that the mod_plainmem and mod_sharedmem modules both have loops like this: if (next) { for (;;) { ... if (!next->next) break; next = next->next; } } which I think might be reduced to: while (next) { ... next = next->next; } I'd also be inclined to remove the sharedmem_getstorage(), sharedmem_initgpool(), and sharedmem_initialize_cleanup() functions, and just in-line their contents in the places they are used. All are used just one time each that I can see. Chris. -- GPG Key ID: 366A375B GPG Key Fingerprint: 485E 5041 17E1 E2BB C263 E4DE C8E3 FA36 366A 375B