Combining adjacent memory blocks
I would suggest keeping a hash table (which you possibly are) to search for addresses.  Use one table for all the different pools.  Do not keep separate tables for each pool.

The idea is that you start from a newly released block. You know its start address and its length.

To find blocks after the newly released one we simply calculate the first address after the block and search for it.  If we find one we join them.

Now we search backwards.

To search backwards our address table must contain not only the start, but the end of blocks.

We search the table for entries matching our end-of-block address.  Again if we find them we join.

If we are doing this check every time we get a newly released block then we do not need to check more than one post- and pre- address block each time.  As these are hash table look-ups they'll be fast.

If we do this check periodically we will need to scan all blocks systematically for matches, including rechecking new joined up super blocks for additional matches.