Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a Fiddle::Pointer#call_free method #36

Merged
merged 10 commits into from
Jun 3, 2020
Merged

Conversation

chrisseaton
Copy link
Contributor

free! runs the user's free function. It doesn't do anything if the pointer has already been freed. The GC won't call the user's free function if the user already manually freed. Another method freed? tells the user if the pointer has already been freed.

All together, these changes allow you to rely on the GC to definitely free your memory, meaning no leaks, but then also allows you to manually free as early as possible if you can, meaning deterministic deallocation, improving cache locality and and reducing peaks in memory use.

@@ -24,6 +24,7 @@ struct ptr_data {
void *ptr;
long size;
freefunc_t free;
int freed;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is int and 1 and 0 the right way to do booleans in C extensions? I found some other things but wasn't sure if they were internal for MRI?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use bool and true and false.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This failed in all CI configurations when I tried it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need #include <stdbool.h>.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@chrisseaton
Copy link
Contributor Author

Some of these Windows CI failures don't make sense to me - some say they can't find libffi and some look like they're testing the system Fiddle, not the Fiddle in this repository.

ptr.free!
assert ptr.freed?
ptr.free! # you can safely run it again
assert ptr.freed?
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do we think free! and freed? should do when there's no free function attached?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about doing nothing?

refute ptr.freed?
ptr.free!
refute ptr.freed?
ptr.free!
refute ptr.freed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that's what we do at the moment. As long as everyone else also thinks that's fine I'll do that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Member

@kou kou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can ignore the "windows mswin" failure for now.
See also: #35

@@ -24,6 +24,7 @@ struct ptr_data {
void *ptr;
long size;
freefunc_t free;
int freed;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use bool and true and false.

begin
assert_equal 10, ptr.size
assert_equal free.to_i, ptr.free.to_i
ensure
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this?
The ptr is freed by GC when on of the above two assertions is failed, isn't it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my mind, it's good practice to free a native, external resource as soon as you can. That's why we use blocks for example when opening a file, rather than letting the GC close it. In the future I hope to create a PR for a similar block scope for pointers as well.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK.
Could you revert these changes in this pull request? We can rewrite our test codes with the block scope free feature in the pull request.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the manual free, leave it relying on GC for now, and then switch to the block malloc that's coming in the next PR? Ok.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.

@@ -20,16 +20,28 @@ def test_malloc_free_func_int
assert_equal free.to_i, Fiddle::RUBY_FREE.to_i

ptr = Pointer.malloc(10, free.to_i)
assert_equal 10, ptr.size
assert_equal free.to_i, ptr.free.to_i
refute ptr.freed?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need test freed? here?
It seems that you add separated tests for it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tidied up.

ptr.free!
assert ptr.freed?
ptr.free! # you can safely run it again
assert ptr.freed?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about doing nothing?

refute ptr.freed?
ptr.free!
refute ptr.freed?
ptr.free!
refute ptr.freed?

Comment on lines 197 to 204
ptr = Pointer.malloc(4, Fiddle::RUBY_FREE)
refute ptr.freed?
ptr.free!
assert ptr.freed?
ptr.free! # you can safely run it again
assert ptr.freed?
GC.start # you can safely run the GC routine
assert ptr.freed?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you split this case to another test?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@@ -370,6 +385,34 @@ rb_fiddle_ptr_free_get(VALUE self)
return rb_fiddle_new_function(address, arg_types, ret_type);
}

/*
* call-seq: free! => nil
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about renaming to call_free or something?
free (getter) and free! will confuse us.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes... I was going for terseness, but I'm open to debating this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's also a destructive operation, which fits the ! pattern.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

free! is destructive but free! is not the destructive version of free.
The relation of them is different from the relation of String#gsub and String#gsub!.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like free! is best due to being short and looking destructive. I'm open to changing it if you're very certain or other people agree.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we have block style free feature (Fiddle::Pointer.malloc(size, Fiddle::RUBY_FREE) {|pointer| ...}?), we'll not call this method manually.
So I don't think we should use shorter name for this method with potential confusion. (Some users may use pointer.free wrongly to free a pointer but the pointer isn't freed. It causes a memory leak.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I'll use call_free.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@kou
Copy link
Member

kou commented May 31, 2020

We can also ignore all Windows jobs...

@MSP-Greg
Copy link
Contributor

MSP-Greg commented Jun 1, 2020

@chrisseaton @kou

I apologize for not looking at the Windows logs in Actions.

All Windows MinGW Ruby versions, 2.4 and later, have a vendored file, rubygems/defaults/operating_system.rb, that then requires files that eventually require fiddle. It's used for a win32 call that adds folders for dll resolution without adding the folder to PATH.

See #37, which disables the loading of operating_system.rb via --disable=gems...

EDIT: I just created a branch with this PR, then added the change in PR #37, and all tests passed (except mswin).

See Actions run:
https://github.com/MSP-Greg/fiddle/runs/727852744
branch commits:
https://github.com/MSP-Greg/fiddle/commits/pr36-win

@chrisseaton chrisseaton requested a review from kou June 2, 2020 13:56
@kou kou changed the title Add a Fiddle::Pointer#free! method Add a Fiddle::Pointer#call_free method Jun 3, 2020
ext/fiddle/pointer.c Outdated Show resolved Hide resolved
ext/fiddle/pointer.c Outdated Show resolved Hide resolved
ext/fiddle/pointer.c Outdated Show resolved Hide resolved
@kou kou merged commit db097cb into ruby:master Jun 3, 2020
@kou
Copy link
Member

kou commented Jun 3, 2020

Thanks!

@chrisseaton chrisseaton deleted the free! branch June 10, 2020 20:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

3 participants