resource scopes and close actions
Michael Zucchi
notzed at gmail.com
Thu Feb 3 03:25:13 UTC 2022
A simple case is AVFormatContext.
Once you open a file you want to scan the stream list which is stored as
an AVStream **. Both need to be segments to use the varhandle api to
access fields.
typedef struct AVFormatContext {
...
unsigned int nb_streams;
/**
* A list of all streams in the file. New streams are created with
* avformat_new_stream().
*
* - demuxing: streams are created by libavformat in
avformat_open_input().
* If AVFMTCTX_NOHEADER is set in ctx_flags, then new
streams may also
* appear in av_read_frame().
* - muxing: streams are created by the user before
avformat_write_header().
*
* Freed by libavformat in avformat_free_context().
*/
AVStream **streams;
...
};
An example public field of AVStream:
typedef struct AVStream {
...
/**
* This is the fundamental unit of time (in seconds) in terms
* of which frame timestamps are represented.
*
* decoding: set by libavformat
* encoding: May be set by the caller before avformat_write_header() to
* provide a hint to the muxer about the desired timebase. In
* avformat_write_header(), the muxer will overwrite this
field
* with the timebase that will actually be used for the
timestamps
* written into the file (which may or may not be related
to the
* user-provided one, depending on the format).
*/
AVRational time_base;
...
};
So my current code generator uses a MemorySegment as the sole 'holder'
for each non-anonymous type, this allows the base constructor just takes
that and i have factory methods which can create the segment from an
address.
public class AVFormatContext {
final MemorySegment segment;
AVFormatContext(MemorySegment segment) {
this.segment = segment;
}
public AVFormatContext create(MemorySegment segment) {
return new ...
}
public AVFormatContext create(MemoryAddress addr, ResourceScope scope) {
return create(MemorySegment.ofAddress(addr, layout.byteSize(), scope);
}
}
Then for the **streams accessor I return a typed List implementation, it
invokes a BiFunction<MemoryAddress,ResourceScope,R> to create instances.
public HandleArray<AVStream> getStreams() {
return
HandleArray.createArray((MemoryAddress)streams$VH.get(segment()),
getNumStreams(), AVStream::create, segment.scope());
}
AVRational is an inline struct so that that also needs it's own segment,
so again the scope gets passed down to getTimeBase() (implicitly though,
via a sliceHandle). So that's that part, it just tracks the same scope
through the process, but this is more of an issue in relation to the
other email. For writing streams you have to create the streams array
and write entries to it as well, so care would have to be taken to use
the same scope for everything that is manually created.
The factory for an AVFormatContext takes an explicit scope parameter
because it's the root 'allocation' if you will, and I was just
experimenting with adding an auto-close feature to it.
static final MethodHandle avformat_open_input$FH =
Memory.downcall("avformat_open_input", FunctionDescriptor.of(Memory.INT,
Memory.POINTER, Memory.POINTER, Memory.POINTER, Memory.POINTER));
public static AVFormatContext openInput(String url, AVInputFormat
fmt, HandleArray<AVDictionary> options, ResourceScope scope$) {
MemoryAddress ps;
int result$;
try (Frame frame$ = Memory.createFrame()) {
PointerArray ps$h = PointerArray.createArray(1, frame$);
result$ =
(int)avformat_open_input$FH.invokeExact((jdk.incubator.foreign.Addressable)Memory.address(ps$h),
(jdk.incubator.foreign.Addressable)frame$.copy(url),
(jdk.incubator.foreign.Addressable)Memory.address(fmt),
(jdk.incubator.foreign.Addressable)Memory.address(options));
ps = ps$h.get(0);
if (result$ == 0) {
AVFormatContext res$ = AVFormatContext.create(ps, scope$);
scope$.addCloseAction(() -> close(ps));
return res$;
}
} catch (Throwable t) {
throw new RuntimeException(t);
}
throw new RuntimeException("error="+result$);
}
public static void close(MemoryAddress s) {...}
So it's only a minor inconvenience - it needs to keep another copy of
the MemoryAddress (ps) around, and it needs to have a static 'close'
function that takes a MemoryAddress rather than a static or
objectfunction that uses 'segment'. The former is just a wasteful copy
and the latter pollutes the api a bit. ffmpeg avformat_close_input
actually takes an AVFormatContext ** and so can be invoked multiple
times safely, so another explicit close function would be useful as well
but that's another matter.
Anyway i'm still trying to work out how to fit this all in, i'm passing
around scopes because MemorySegment.ofAddress() needs it and now i'm
trying to work out how to fit them to the lifecycle of objects. This
was the first attempt at utilising the facilities for some benefit.
But it seems very difficult to get right and somewhat clumsy to use and
only provides a very marginal increase in live-ness safety because you
can always turn a MemoryAddress into a MemorySegment in another scope.
And ideally it's not something you'd expose to the api user. With JNI
there were other more straightforward mechanisms to manage the java<>c
lifecycle matching (e.g. nulling out the pointer), ones that could be
hidden from the library user.
I'm starting to think of changing to use globalScope() and to mostly
pretend ResourceScope doesn't exist. For handle-based apis (anonymous
pointers) like OpenCL it's already effectively the case anyway.
On 2/2/22 21:20, Maurizio Cimadamore wrote:
> Hi Michael,
> I understand the concern. Running close actions _after_ the scope is
> closed (and so segments associated with it are inaccessible) is a sort
> of a forced move: you need to run the close actions when you are 100%
> sure that nobody else is accessing the scope. Otherwise you might have
> a race between a close action attempting to free some resource and
> another thread attempting to access it.
>
> The result is that, as you have noticed, adding close actions is
> mostly useful when working with APIs that turn raw MemoryAddress into
> segments manually (MemorySegment::ofAddress).
>
> Can you please share a problematic case with ffmpeg?
>
> Thanks
> Maurizio
>
> On 02/02/2022 01:34, Michael Zucchi wrote:
>>
>> Morning,
>>
>> I was hoping to use resource scopes to auto-cleanup native-allocated
>> resources, but it looks like it's going to be a bit messy because
>> segments get closed before their close actions are invoked.
>>
>> Is this the intended behaviour or a side-effect of 'no particular
>> order'?
>>
>> For some api's it doesn't matter as they don't use api-allocated
>> public structures (e.g. opencl) and the same approach is fine using
>> MemoryAddress, but for others it's an issue (e.g. ffmpeg) where the
>> obvious mapping is to use a segment and varhandles.
>>
>> Probably as a workaround - if i want to have this feature at any rate
>> because i'm not sure if it's a good idea yet - i'll just save a copy
>> of the address before it's turned into a segment and pass that to any
>> close function instead.
>>
>> I've only really started looking at seeing how to 'scope' things
>> 'properly' but it feels a bit clumsy and sort of not worth it because
>> it has to be worked around all the time either with hacks like this
>> or resorting to simply using the global scope which effectively does
>> nothing.
>>
>> Z
>>
>>
More information about the panama-dev
mailing list