-
Notifications
You must be signed in to change notification settings - Fork 4
[WIP] Ruby bindings #67
base: master
Are you sure you want to change the base?
Conversation
At this point we have: p = Arbiter::ProjectIdentifier.new('yolo')
r = Arbiter::Requirement.at_least(Arbiter::SemanticVersion.new(1, 2, 3))
d = p.create_dependency(r) |
} | ||
|
||
static void project_identifier_mark(ArbiterUserValue *user_value) { | ||
rb_gc_mark((VALUE)user_value->data); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my irb
sessions, there's an infrequent crash happening here. Perhaps data
needs to be nilled out in the free function passed to Data_Wrap_Struct
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem was the parameter type, it should be ArbiterProjectIdentifier *
🙈. Someone should create a TypedC.
|
||
static VALUE requirement_compatible_with(VALUE klass, VALUE version) { | ||
ArbiterSemanticVersion *semantic_version = DATA_PTR(version); | ||
ArbiterRequirement *requirement = ArbiterCreateRequirementCompatibleWith(semantic_version, ArbiterRequirementStrictnessAllowVersionZeroPatches); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ruby doesn't need no ArbiterRequirementStrictnessStrict
, right?
.lessThan = value_less_than, | ||
.hash = value_hash, | ||
.createDescription = value_create_description, | ||
.destructor = NULL, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm… this seems wrong, and might relate to the crashes you're seeing. Although Ruby is GCed, you're providing data to Arbiter here, so you need to effectively add a new GC root while Arbiter has a reference to it, and then remove it once Arbiter invokes your destructor.
Are there APIs available to do that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
once Arbiter invokes your destructor
when can this happen? (outside of this code calling ArbiterFree()
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Arbiter takes responsibility for user values as soon as they're passed into any Arbiter function. It will invoke the destructor when it's done with it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It will invoke the destructor when it's done with it.
I didn't realize that. The way things are structured in this diff, the life of the user value is tied to the life of project identifier. That could lead to (possibly indefinite) delayed cleanup of the user value's resources, but I don't see how that would cause a crash. Do you?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the Ruby object is GCed before Arbiter is done with the user value pointer, that could cause a crash. Since Arbiter treats user values (and project identifiers, versions, etc.) like, well, values, it'd be pretty easy for their lifetimes to not align.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Correct Way™ to do this is remove the object from Ruby's GC while Arbiter is using it, then re-insert it afterward.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the Ruby object is GCed before Arbiter is done with the user value pointer
The function project_identifier_mark()
is there to prevent that from happening. I will rename it to *_gc_mark
.
Question. You said:
Arbiter takes responsibility for user values as soon as they're passed into any Arbiter function. It will invoke the destructor when it's done with it.
But with the existence of ArbiterProjectIdentifierValue()
, will the user value data be kept alive for as long as the ArbiterProjectIdentifier
? This diff assumed this to be true.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will the user value data be kept alive for as long as the
ArbiterProjectIdentifier
?
Yes, but this isn't explicitly codified, and the value may outlive the project identifier if it gets copied behind-the-scenes.
|
||
require 'mkmf' | ||
|
||
$LDFLAGS << ' -lc++' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW I ran into some problems on Linux trying to use this flag. I think you just need to compile with a C++ compiler if possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this wouldn't work where there's only libstdc++
. I thought about using clang++
but wasn't sure I want to enable the whole bundle of implicit flags turned on by that, vs the actual need of linking to a C++. I can use have_library()
to check for libc++
first then try libstdc++
second.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the whole bundle of implicit flags turned on by that
What are those? Are they flags we would want anyways when linking a C++ library?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question, I'll check (with clang on OS X) tomorrow and report back.
Part of the concern was not what it is now, it was lack of visibility. Between compilers, between versions, between targets, the set of flags determined by the driver will change, but users aren't really aware of those flags, and how they change. Is it better to depend on the compiler always knowing best, or better to try to leave the compiler out and pass the one library to the linker?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using Apple's clang that comes with Xcode 7.3.1, the difference between clang++ and clang is that clang++ adds the following compiler flags:
-fdeprecated-macro
-fexceptions
-fcxx-exceptions
-stdlib=libc++
Enabling exceptions would make the binary bigger by some amount. I don't know if -stdlib=libc++
has any effect on compilation, or if it's more for header search paths and linking.
EDIT: Forgot -x c
vs -x c++
. I don't know where C/C++ differ in terms of spec, but I assume this Ruby extension wouldn't be impacted by differences in language semantics.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We actually want exceptions enabled when linking statically, because Arbiter relies upon them internally.
The "real" answer here is that Arbiter should be dynamically linked in most cases—I just set up static linking first because it was easier.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does enabling of exceptions depend on the linking?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose that as long as Arbiter sources are compiled with -fexceptions
, it shouldn't matter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just compiled with clang++
and there were 6 warnings and 11 errors. Also this message:
clang: warning: treating 'c' input as 'c++' when in C++ mode, this behavior is deprecated
the warnings were "designated initializers are a C99 feature" and the errors were from the generic (i.e. varargs) function pointer type taken by rb_define_method()
and others like it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
¯_(ツ)_/¯
This diff adds a `.destructor` callback to the user value received by ArbiterCreateProjectIdentifier(). This change is to provide precise lifetime control of those user values. This change isn't as simple as I would have liked it to be. The crux of these changes it that the user value is now additionally stored in the Ruby data object, so that the `.destructor` can nil it out. This one change resulted in most of the changes in this commit: 1. Introduction of `ProjectIdentifierData` 2. User value `.data` changed from `VALUE` to `VALUE *`
This adds a basic Ruby extension. The initial direction on this is to make a superficial Ruby API that does minimally more than wrapping the C API.
TODO
Makefile
(and maybeCMakeLists.txt
)RData
type checkingrb_gc_mark
crash