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

Split semian operations into more cohesive translation units #101

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@
/html/
Gemfile.lock
vendor/
*.swp
Copy link
Member Author

Choose a reason for hiding this comment

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

sorry, i'm a vim junky.

*.swo
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ language: ruby
sudo: true

before_install:
- gem update --system
Copy link
Member Author

Choose a reason for hiding this comment

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

This fixes CI which was failing on installing native extensions for rainbows

- gem install bundler
- scripts/install_toxiproxy.sh

Expand Down
262 changes: 262 additions & 0 deletions ext/semian/resource.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
#include "resource.h"

// "Private" function forward declarations
static VALUE
cleanup_semian_resource_acquire(VALUE self);

static int
check_tickets_arg(VALUE tickets);

static long
check_permissions_arg(VALUE permissions);

static const
char *check_id_arg(VALUE id);

static double
check_default_timeout_arg(VALUE default_timeout);

static void
ms_to_timespec(long ms, struct timespec *ts);

static const rb_data_type_t
semian_resource_type;


VALUE
semian_resource_acquire(int argc, VALUE *argv, VALUE self)
{
semian_resource_t *self_res = NULL;
semian_resource_t res = { 0 };

if (!rb_block_given_p()) {
rb_raise(rb_eArgError, "acquire requires a block");
}

TypedData_Get_Struct(self, semian_resource_t, &semian_resource_type, self_res);
res = *self_res;

/* allow the default timeout to be overridden by a "timeout" param */
if (argc == 1 && TYPE(argv[0]) == T_HASH) {
VALUE timeout = rb_hash_aref(argv[0], ID2SYM(id_timeout));
if (TYPE(timeout) != T_NIL) {
if (TYPE(timeout) != T_FLOAT && TYPE(timeout) != T_FIXNUM) {
rb_raise(rb_eArgError, "timeout parameter must be numeric");
}
ms_to_timespec(NUM2DBL(timeout) * 1000, &res.timeout);
}
} else if (argc > 0) {
rb_raise(rb_eArgError, "invalid arguments");
}

/* release the GVL to acquire the semaphore */
acquire_semaphore_without_gvl(&res);
if (res.error != 0) {
if (res.error == EAGAIN) {
rb_raise(eTimeout, "timed out waiting for resource '%s'", res.name);
} else {
raise_semian_syscall_error("semop()", res.error);
}
}

return rb_ensure(rb_yield, self, cleanup_semian_resource_acquire, self);
}

VALUE
semian_resource_destroy(VALUE self)
{
semian_resource_t *res = NULL;

TypedData_Get_Struct(self, semian_resource_t, &semian_resource_type, res);
if (semctl(res->sem_id, SI_NUM_SEMAPHORES, IPC_RMID) == -1) {
raise_semian_syscall_error("semctl()", errno);
}

return Qtrue;
}

VALUE
semian_resource_count(VALUE self)
{
int ret;
semian_resource_t *res = NULL;

TypedData_Get_Struct(self, semian_resource_t, &semian_resource_type, res);
ret = semctl(res->sem_id, SI_SEM_TICKETS, GETVAL);
if (ret == -1) {
raise_semian_syscall_error("semctl()", errno);
}

return LONG2FIX(ret);
}

VALUE
semian_resource_id(VALUE self)
{
semian_resource_t *res = NULL;
TypedData_Get_Struct(self, semian_resource_t, &semian_resource_type, res);
return LONG2FIX(res->sem_id);
}

VALUE
semian_resource_initialize(VALUE self, VALUE id, VALUE tickets, VALUE permissions, VALUE default_timeout)
{
key_t key;
int c_permissions;
double c_timeout;
int c_tickets;
int created = 0;
semian_resource_t *res = NULL;
const char *c_id_str = NULL;

// Check and cast arguments
c_tickets = check_tickets_arg(tickets);
c_permissions = check_permissions_arg(permissions);
c_id_str = check_id_arg(id);
c_timeout = check_default_timeout_arg(default_timeout);

// Build semian resource structure
TypedData_Get_Struct(self, semian_resource_t, &semian_resource_type, res);

// Populate struct fields
ms_to_timespec(c_timeout * 1000, &res->timeout);
res->name = strdup(c_id_str);

// Get or create semaphore set
// note that tickets = 0 will be used to acquire a semaphore set after it's been created elswhere
key = generate_sem_set_key(c_id_str);
res->sem_id = c_tickets == 0 ? get_semaphore(key) : create_semaphore(key, c_permissions, &created);
if (res->sem_id == -1) {
raise_semian_syscall_error("semget()", errno);
}

set_semaphore_permissions(res->sem_id, c_permissions);

// Configure semaphore ticket counts
configure_tickets(res->sem_id, c_tickets, created);

return self;
}

VALUE
semian_resource_alloc(VALUE klass)
{
semian_resource_t *res;
VALUE obj = TypedData_Make_Struct(klass, semian_resource_t, &semian_resource_type, res);
return obj;
}
/*
*********************************************************************************************************
"Private"

These functions are specific to semian resource interals and may not be called by other files
*********************************************************************************************************
*/

static VALUE
cleanup_semian_resource_acquire(VALUE self)
{
semian_resource_t *res = NULL;
TypedData_Get_Struct(self, semian_resource_t, &semian_resource_type, res);
if (perform_semop(res->sem_id, SI_SEM_TICKETS, 1, SEM_UNDO, NULL) == -1) {
res->error = errno;
}
return Qnil;
}

static long check_permissions_arg(VALUE permissions)
{
Check_Type(permissions, T_FIXNUM);
return FIX2LONG(permissions);
}

static int check_tickets_arg(VALUE tickets)
{
int c_tickets;

if (TYPE(tickets) != T_NIL) {
if (TYPE(tickets) == T_FLOAT) {
rb_warn("semian ticket value %f is a float, converting to fixnum", RFLOAT_VALUE(tickets));
tickets = INT2FIX((int) RFLOAT_VALUE(tickets));
}
Check_Type(tickets, T_FIXNUM);

if (FIX2LONG(tickets) < 0 || FIX2LONG(tickets) > system_max_semaphore_count) {
rb_raise(rb_eArgError, "ticket count must be a non-negative value and less than %d", system_max_semaphore_count);
}
c_tickets = FIX2LONG(tickets);
} else {
c_tickets = -1;
}

return c_tickets;
}

static const char* check_id_arg(VALUE id)
{
const char *c_id_str = NULL;

if (TYPE(id) != T_SYMBOL && TYPE(id) != T_STRING) {
rb_raise(rb_eTypeError, "id must be a symbol or string");
}
if (TYPE(id) == T_SYMBOL) {
c_id_str = rb_id2name(rb_to_id(id));
} else if (TYPE(id) == T_STRING) {
c_id_str = RSTRING_PTR(id);
}

return c_id_str;
}

static double check_default_timeout_arg(VALUE default_timeout)
{
if (TYPE(default_timeout) != T_FIXNUM && TYPE(default_timeout) != T_FLOAT) {
rb_raise(rb_eTypeError, "expected numeric type for default_timeout");
}

if (NUM2DBL(default_timeout) < 0) {
rb_raise(rb_eArgError, "default timeout must be non-negative value");
}
return NUM2DBL(default_timeout);
}

static void
ms_to_timespec(long ms, struct timespec *ts)
{
ts->tv_sec = ms / 1000;
ts->tv_nsec = (ms % 1000) * 1000000;
}

static inline void
semian_resource_mark(void *ptr)
{
/* noop */
}

static inline void
semian_resource_free(void *ptr)
{
semian_resource_t *res = (semian_resource_t *) ptr;
if (res->name) {
free(res->name);
res->name = NULL;
}
xfree(res);
}

static inline size_t
semian_resource_memsize(const void *ptr)
{
return sizeof(semian_resource_t);
}

static const rb_data_type_t
semian_resource_type = {
"semian_resource",
{
semian_resource_mark,
semian_resource_free,
semian_resource_memsize
},
NULL, NULL, RUBY_TYPED_FREE_IMMEDIATELY
};
75 changes: 75 additions & 0 deletions ext/semian/resource.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
For core semian resource functions exposed directly to ruby.

Functions here are associated with rubyland operations.
*/
#ifndef SEMIAN_RESOURCE_H
#define SEMIAN_RESOURCE_H

#include "types.h"
#include "sysv_semaphores.h"
#include "tickets.h"

// Ruby variables
ID id_timeout;
int system_max_semaphore_count;

/*
* call-seq:
* Semian::Resource.new(id, tickets, permissions, default_timeout) -> resource
*
* Creates a new Resource. Do not create resources directly. Use Semian.register.
*/
VALUE
semian_resource_initialize(VALUE self, VALUE id, VALUE tickets, VALUE permissions, VALUE default_timeout);

/*
* call-seq:
* resource.acquire(timeout: default_timeout) { ... } -> result of the block
*
* Acquires a resource. The call will block for <code>timeout</code> seconds if a ticket
* is not available. If no ticket is available within the timeout period, Semian::TimeoutError
* will be raised.
*
* If no timeout argument is provided, the default timeout passed to Semian.register will be used.
*
*/
VALUE
semian_resource_acquire(int argc, VALUE *argv, VALUE self);

/*
* call-seq:
* resource.destroy() -> true
*
* Destroys a resource. This method will destroy the underlying SysV semaphore.
* If there is any code in other threads or processes blocking or using the resource
* they will likely raise.
*
* Use this method very carefully.
*/
VALUE
semian_resource_destroy(VALUE self);

/*
* call-seq:
* resource.count -> count
*
* Returns the current ticket count for a resource.
*/
VALUE
semian_resource_count(VALUE self);

/*
* call-seq:
* resource.semid -> id
*
* Returns the SysV semaphore id of a resource.
*/
VALUE
semian_resource_id(VALUE self);

// Allocate a semian_resource_type struct for ruby memory management
VALUE
semian_resource_alloc(VALUE klass);

#endif //SEMIAN_RESOURCE_H
Loading