-
Notifications
You must be signed in to change notification settings - Fork 85
Proposed SysV structures and Mixin functionality #64
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
Conversation
6a888c3 to
bcb187d
Compare
lib/semian/simple_integer.rb
Outdated
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.
You don't need the _ here. If you disregard everything.
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.
So just def initialize(**) ? I didn't know that.
Fine by me. As said though
Synchronize is definitely much better.
Nah, an accessor is always good. The rest of the PR is out of my my expertise. |
|
@csfrancis Any suggestions or comments on the C side? A working version can be found here in the old branch which I keep updated, but probably reading the long PR description up there ^ would be enough. |
lib/semian/sysv_shared_memory.rb
Outdated
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.
@byroot probably the most interesting change that you could provide an opinion for is this function here. I call it from C to replace a regular accessor or setter that accesses shared memory and is not a atomic call, and wrap it in a synchronize. I wrap all the functions with this, so it's essentially a do_with_sync :value=, :value, :reset, :increment and do_with_sync :size=, :max_size, :<<, :push, :pop etc
I just want to make sure there are no disagreements here.
Relevant C code is
https://github.com/Shopify/semian/blob/circuit-breaker-per-host/ext/semian/semian_shared_memory_object.c#L319-L324
where define_method_with_synchronize calls do_with_sync
and
https://github.com/Shopify/semian/pull/54/files#diff-99c3116b9c2c62cc89a4ae4cc84fadaaR262
which is where define_method_with_synchronize is used in place of rb_define_method
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'd prefer to avoid this kind of monkey patching.
I'd rather prefer have a noop sychronize method in Simple.
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.
While looking over the C code base, one of the scarier things was that because I provided the lock() and unlock() as separate functions, it was very difficult to track where I had to re-unlock, since errors could occur in the middle of a function, and then you'd have to return right there, and insert an unlock() there. It just isn't safe to have many exit points sprawled all over the code. So I then opted to write ensures in, but doing so manually would add twice the number of functions and make things very manual.
Here's the what a begin-ensure would look like:
return rb_ensure(semian_shm_object_synchronize_with_block, self, semian_shm_object_synchronize_restore_lock_status, (VALUE)&status);For reference: https://github.com/Shopify/semian/pull/54/files#diff-905e62ddb202d77b001ca1dbac3cbf06R310
Essentially each function needed a wrapper, and probably also some custom struct to overcome the one-argument limit of the function rb_ensure. This naturally lead to me doing it in Ruby instead since it doesn't have these limitations, which is why synchronize does what it does. IMO it's not a big deal since it only does it once per class, reduces code and complexity by a bit. Maybe using prepend with its use of super would make it cleaner than doing what I do currently, which is having an #{method_name}_inner as the original and ##{method_name} as the synchronized version.
With regards to the noop synchronize, do you mean something more along the lines of (1) , with SharedMemoryObject -> Simple::Integer -> SysV::Integer ? Otherwise I don't understand what you mean by noop synchronize. Unless you mean just having one there to keep the interfaces the same?
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.
@kyewei I think what he means with noop syncronize is that the SysV implementations continue to inherit from Simple::Integer and friends, which then look like this:
class Semian::Simple::Integer
# .. stuff
def increment(delta)
synchronize { value += delta }
end
private
def synchronize
yield
end
# more stuff
endWhen this mixin is included, it just overrides it. The implementation can then be simplified when all the fallback checking is done at boot-time because you can just override it in C, and not have it in Ruby.
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 see what you're saying, but that also doesn't work in protecting lets say.. Semian::SysV::Integer#value. If the #value is not protected in C code, and it directly replaces the SysV::Integer, there is no entry point to override and replace with synchronize { ... } except through what do_with_sync does. If you do protect in C, well, that goes back to the problem of making 2x the functions to call rb_ensure(callback, arg1, ensure_fn, arg2)
For example, in code,
def increment(val=1)
synchronize { self.value += 1 } # OK
end
def value
#how do you put a synchronize in here?
# you can't do self.value, that would just do recursion and crash
@value
end
lib/semian.rb
Outdated
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.
Should these requires be conditional depending on whether the target platform supports SysV?
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.
Agree.
|
@sirupsen wanted to see the tests, so I'll include them now. Some of those new tests will fail because the C component isn't included in this PR :( They do succeed on the other branch however. |
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 do you need this now?
|
I think that 2 is the right approach as well. |
lib/semian/sysv_shared_memory.rb
Outdated
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 not just use @type_size[type.to_sym] and make this easier to read? I assume the only reason you need the respond_to? is because you don't have the C implementation just yet?
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's for memoizing the sizes after it's read once from C. I don't want to hardcode sizes, they differ by platform.
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 agree with that, but I don't see why you need the local variable when you have the class instance variable.
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.
Oh. For raising the TypeError. Maybe that should be moved to C instead?
lib/semian/sysv_shared_memory.rb
Outdated
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 sizeof need to be a class method?
|
Fundamentally I think what makes this PR more complicated than it needs to be is that the fallback is done within the SysV driver, instead of at boot time as @csfrancis also pointed out. This will simplify I bunch of things because you can make more assumptions. |
test/sysv_sliding_window_test.rb
Outdated
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 is a little bit of a nitpick, but I'd call this KLASS instead of make it a method. CLASS is too close to the reserved keyword.
c300b81 to
89075a2
Compare
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 you did alias_method :to_i, :value and always called value.to_i in Simple::State, you wouldn't need to override these. Duck typing hard!
Old massive PR: #54
Small PR 1: #62
Possibly outstanding issues
SysVversions of the structure needing permissions and a name,Semian::Simple::SlidingWindow.new(max_size: 4)andSemian::SysV::SlidingWindow.new(max_size: 4, permissions: 0660, name: "sample_int")have different interfaces. Ways to solvedef initialize(max_size: , **)current choicedef initialize(options = {})or something of the sortpermissionsandnameat all, but they have to come from somewhere else without leaking the implementation, possible fromSemian.registerTestCircuitBreakeriscircuit_breaker_test.rbexecute_atomically: you know what, I changed it to#synchronize, similar to classes likeMutexandMonitor. It's still aliased to#transaction#shared?It's private, and just returns@using_shared_memory. Hopefully won't ruffle any feathers, or maybe just theivaris enough?nameandpermissionsto the structures was bad. Personally I disagree, but if so, start a discussion below about it.Content
Alright, here's what I propose for adding in the SysV structures. I didn't include the tests yet, they will obviously fail because there's no C code to provide actual shared memory in this commit.
The three
SysVclasses inherit directly from theSimpleequivalents. They also mixin theSemian::SysVSharedMemorymodule, which provides the common functionality.From the C perspective, to get the memory allocation and things to work, it needs to do a bunch of things. Certain ways of accomplishing them are IMO worse than others, but I'll list the things that the C side needs to do, and the different ways to do them, and why/how I decided on this way.
C needs to accomplish:
acquire_memory_objectis called. The C code needs this when unpacking the RubyVALUE selfobject into a proper Csemian_shm_object *#acquire_shared_memoryat a shared level#value=,#<<, etcThis is about to get heavy.
There were 3 ways of doing the above, and there is a diamond problem here. I originally had (1), but I changed to (2).
(1): Have a superclass
SharedMemoryObject, andSimplestructures inherit from that. TheSysVfurther inherit from theSimpleclass Simple::Integer < SharedMemoryObjecteven though it doesn't do sharing at all(2): Mixin a
SysVSharedMemorymodule that does shares common functionality into theSysVvariants onlyPros: Makes sense from class organization perspective, mixin a module if you want shared-ness
Cons: Although you can hook methods like
acquire_memory_objectin one spot, you have to replace the three different allocation methods separately. There is also a timing issue involved. You can't simply do this:Instead, as I found out, you had to do something like this in the C extension:
(3): Make the
SysVclasses inherit fromSharedMemoryObject. But what do we do about the common functionality betweenSimpleandSysVversions? Put that in a module, and include it into things that need the functionality, like this:If you made it this far, 👍, it was really long.
I chose (2) because it's the most structurally clean, and I have a working solution to the timing problem, and there are no random abuses of Ruby features like Mixins beyond what is necessary.
I've isolated the locking/unlocking, and by doing that, eliminated the lock() and unlock() functions for a synchronize(), and using some delegating I wrap the methods with lock() and
ensureunlock().Here's the typical structure of an
acquirethat encompasses the C features as well just so we're on the same page:Here's the structure of a synchronized access. Any method defined using
define_method_with_synchronizeis synchronized.define_method_with_synchronizebasically defines the original method, and then callsdo_with_sync :method_nameDiscussion, opinions, etc would be great. I also want suggestions as to how to go forward with this.
A version with everything working is on the branch with the large PR, #54
@sirupsen @byroot