-
Notifications
You must be signed in to change notification settings - Fork 682
Description
Support for custom allocators inside harfbuzz were just added on harfbuzz recently and as main clients of harfbuzz use it on single threaded fashion and with no-custom allocators, the following issue probably has not high priority but would be nice if we can improve the situation.
I was interested on the comment that is put here and whether we can improve the situation for them so I asked the commentator for more explanation on an email and took permission to publish it here,
We use Harfbuzz in MuPDF. I'll explain the biggest problem we have with it.
In common with many projects out there MuPDF has it's own allocation system - so everything should call fz_malloc and fz_free, rather than normal malloc and free.
i.e. we need to make everything allocate through:
void *fz_malloc(fz_context *ctx, size_t size);This includes Harfbuzz's allocations. Now, Harfbuzz tries to accomodate us - we can set hb_malloc_impl=hb_malloc and hb_free_impl=hb_free etc during compilation, to direct calls to hb_malloc and hb_free functions that we supply, that call down to the MuPDF allocators.
So we define:
void *hb_malloc(size_t size) { return fz_malloc( ???? , size); }But what goes ???? here?
If MuPDF were single threaded, then we could have:
static fz_context *hb_ctx;and then have:
void *hb_malloc(size_t size) { return fz_malloc(hb_ctx, size); }and as long as we set hb_ctx before we call into Harfbuzz, we'd be fine.
Unfortunately, for multi-threaded stuff this doesn't work, because the fz_context is (and needs to be) different in every thread.
We can't safely use a single static fz_context pointer in a multi-threaded build.
We could use thread local storage, but that's a problem for us. So far we've managed to avoid relying on that, and it doesn't fit nicely into our use of threading constructs, so we've gone for a different solution.
Currently we simply take and drop a mutex around all calls to harfbuzz.
This means that only 1 thread can be calling into harfbuzz at a time, and we can safely get away with a single static fz_context. It's not a nice solution though.As another point - It might be nice to work with a system level shared Harfbuzz library, but this can never work, because the system level lib won't redirect hb_malloc and hb_free as we need.
Proposed solution:
Well, having thought about it, there is a simple (but tedious) fix that addresses ALL these issues, preserves the ABI, and is keeping with what the rest of the world does with such libraries.
Suppose we have a harfbuzz API function:
hb_wibble(A, B, C)Rename that, and add 1 extra param to make it:
hb_wibble_threaded(hb_alloc_ctx *ctx, A, B, C)then add:
hb_wibble(A, B, C) { return hb_wibble_threaded(NULL, A, B, C); }So the overall API is still preserved (at the cost of a tiny stub function), but we've gained another API that we can call that is thread safe.
We then work our way through hb_wibble_threaded arranging for 'ctx' to be passed through all the way down to point at which we call malloc or free, where we do something like:
if (ctx) return ctx->malloc(ctx->opaque, size); else return malloc(size);(We can wrap this as malloc_shim or something)
So the idea is that 'ctx' holds the allocator/deallocator functions, and the context for the calling code (ctx->opaque).
Essentially at the end of it, we end up with Harfbuzz being structured exactly as before, but now offering 2 APIs - one threadsafe, one not.
In addition Harfbuzz can now safely be used as a system level library as things can call it via the threadsafe API and still have control of where the underlying allocations go.
The cost in size should be trivial, and the cost in maintainability is very low too - all that we lose is the need to pass this extra context down through the code.