Skip to content

Commit

Permalink
Grow/shrink the sliding window's max_size
Browse files Browse the repository at this point in the history
  • Loading branch information
Michael Kipper committed Jun 18, 2019
1 parent 15ab4bd commit 1a46fb6
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 19 deletions.
98 changes: 93 additions & 5 deletions ext/semian/sliding_window.c
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,71 @@ check_max_size_arg(VALUE max_size)
return retval;
}

static VALUE
grow_window(semian_simple_sliding_window_shared_t* window, int new_max_size)
{
if (new_max_size > SLIDING_WINDOW_MAX_SIZE) return Qnil;

if (window->end > window->start) {
// Easy case - the window doesn't wrap around
if (window->length) window->end = window->start + window->length;
} else {
// Hard case - the window wraps, and data might need to move
int offset = new_max_size - window->max_size;
for (int i = offset - window->start - 1; i >= 0; --i) {
int srci = window->start + i;
int dsti = window->start + offset + i;
window->data[dsti] = window->data[srci];
}
window->start += offset;
}

window->max_size = new_max_size;

return RB_INT2NUM(new_max_size);
}

static void swap(int *a, int *b) {
int c = *a;
*a = *b;
*b = c;
}

static VALUE
shrink_window(semian_simple_sliding_window_shared_t* window, int new_max_size)
{
if (new_max_size > SLIDING_WINDOW_MAX_SIZE) return Qnil;

int new_length = (new_max_size > window->length) ? window->length : new_max_size;

if (window->end > window->start) {
// Easy case - the window doesn't wrap around
window->end = window->start + new_length;
} else {
// Hard case - the window wraps, so re-index the data
// Adapted from http://www.cplusplus.com/reference/algorithm/rotate/
int first = 0;
int middle = window->start;
int last = window->max_size;
int next = middle;
while (first != next) {
swap(&window->data[first++], &window->data[next++]);
if (next == last) {
next = middle;
} else if (first == middle) {
middle = next;
}
}
window->start = 0;
window->end = new_length;
}

window->max_size = new_max_size;
window->length = new_length;

return RB_INT2NUM(new_max_size);
}

// Get the C object for a Ruby instance
static semian_simple_sliding_window_t*
get_object(VALUE self)
Expand All @@ -81,6 +146,7 @@ Init_SlidingWindow()
rb_define_alloc_func(cSlidingWindow, semian_simple_sliding_window_alloc);
rb_define_method(cSlidingWindow, "initialize_sliding_window", semian_simple_sliding_window_initialize, 2);
rb_define_method(cSlidingWindow, "size", semian_simple_sliding_window_size, 0);
rb_define_method(cSlidingWindow, "resize_to", semian_simple_sliding_window_resize_to, 1);
rb_define_method(cSlidingWindow, "max_size", semian_simple_sliding_window_max_size, 0);
rb_define_method(cSlidingWindow, "values", semian_simple_sliding_window_values, 0);
rb_define_method(cSlidingWindow, "last", semian_simple_sliding_window_last, 0);
Expand Down Expand Up @@ -115,11 +181,13 @@ semian_simple_sliding_window_initialize(VALUE self, VALUE name, VALUE max_size)
dprintf("Setting max_size for '%s' to %d", to_s(name), max_size_val);
res->shmem->max_size = max_size_val;
} else if (res->shmem->max_size != max_size_val) {
// TODO(michaelkipper): Figure out what do do in this case...
printf("Warning: Max size of %d is different than current value of %d", max_size_val, res->shmem->max_size);
// dprintf("max_size %d is different than %d", max_size_val, res->shmem->max_size);
// sem_meta_unlock(res->sem_id);
// rb_raise(rb_eArgError, "max_size was different");
if (max_size_val > res->shmem->max_size) {
grow_window(res->shmem, max_size_val);
} else {
// TODO(michaelkipper): Figure out what do do in this case...
printf("Warning: Shrinking window from %d to %d", res->shmem->max_size, max_size_val);
shrink_window(res->shmem, max_size_val);
}
}
}
sem_meta_unlock(res->sem_id);
Expand All @@ -142,6 +210,26 @@ semian_simple_sliding_window_size(VALUE self)
return retval;
}

VALUE
semian_simple_sliding_window_resize_to(VALUE self, VALUE new_size)
{
semian_simple_sliding_window_t *res = get_object(self);
VALUE retval = Qnil;

int new_max_size = RB_NUM2INT(new_size);
sem_meta_lock(res->sem_id);
{
if (new_max_size > res->shmem->max_size) {
retval = grow_window(res->shmem, new_max_size);
} else if (new_max_size < res->shmem->max_size) {
retval = shrink_window(res->shmem, new_max_size);
}
}
sem_meta_unlock(res->sem_id);

return retval;
}

VALUE
semian_simple_sliding_window_max_size(VALUE self)
{
Expand Down
1 change: 1 addition & 0 deletions ext/semian/sliding_window.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ void Init_SlidingWindow();
VALUE semian_simple_sliding_window_alloc(VALUE klass);
VALUE semian_simple_sliding_window_initialize(VALUE self, VALUE name, VALUE max_size);
VALUE semian_simple_sliding_window_size(VALUE self);
VALUE semian_simple_sliding_window_resize_to(VALUE self, VALUE new_size);
VALUE semian_simple_sliding_window_max_size(VALUE self);
VALUE semian_simple_sliding_window_push(VALUE self, VALUE value);
VALUE semian_simple_sliding_window_values(VALUE self);
Expand Down
8 changes: 2 additions & 6 deletions ext/semian/sysv_semaphores.c
Original file line number Diff line number Diff line change
Expand Up @@ -162,9 +162,7 @@ acquire_semaphore(void *p)
semian_resource_t *res = (semian_resource_t *) p;
res->error = 0;
res->wait_time = -1;
#ifdef DEBUG
print_sem_vals(res->sem_id);
#endif
dprint_sem_vals(res->sem_id);

struct timespec begin, end;
int benchmark_result = clock_gettime(CLOCK_MONOTONIC, &begin);
Expand All @@ -191,9 +189,7 @@ initialize_new_semaphore_values(int sem_id, long permissions)
if (semctl(sem_id, 0, SETALL, init_vals) == -1) {
raise_semian_syscall_error("semctl()", errno);
}
#ifdef DEBUG
print_sem_vals(sem_id);
#endif
dprint_sem_vals(sem_id);
}

static int
Expand Down
4 changes: 1 addition & 3 deletions ext/semian/sysv_semaphores.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,8 @@ acquire_semaphore_without_gvl(void *p);
int
initialize_single_semaphore(uint64_t key, long permissions);

#ifdef DEBUG
static inline void
print_sem_vals(int sem_id)
dprint_sem_vals(int sem_id)
{
dprintf("sem_id: %d, lock: %d, tickets: %d configured: %d, registered workers %d",
sem_id,
Expand All @@ -127,6 +126,5 @@ print_sem_vals(int sem_id)
get_sem_val(sem_id, SI_SEM_REGISTERED_WORKERS)
);
}
#endif

#endif // SEMIAN_SEMSET_H
4 changes: 1 addition & 3 deletions ext/semian/tickets.c
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,7 @@ update_ticket_count(int sem_id, int tickets)

delta = tickets - get_sem_val(sem_id, SI_SEM_CONFIGURED_TICKETS);

#ifdef DEBUG
print_sem_vals(sem_id);
#endif
dprint_sem_vals(sem_id);
if (perform_semop(sem_id, SI_SEM_TICKETS, delta, 0, &ts) == -1) {
if (delta < 0 && errno == EAGAIN) {
rb_raise(eTimeout, "timeout while trying to update ticket count");
Expand Down
2 changes: 2 additions & 0 deletions test/resource_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@

class TestResource < Minitest::Test
include ResourceHelper

def setup
@workers = []
Semian.destroy(:testing)
rescue
nil
Expand Down
107 changes: 105 additions & 2 deletions test/simple_sliding_window_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,113 @@ def resize_to_less_than_1_raises
end
end

def test_resize_to_simple
id = Time.now.strftime('%H:%M:%S.%N')
window = ::Semian::ThreadSafe::SlidingWindow.new(id, max_size: 4)
window << 0 << 1
assert_sliding_window(window, [0, 1], 4)
window.resize_to(8)
assert_sliding_window(window, [0, 1], 8)
window << 2 << 3 << 4 << 5
assert_sliding_window(window, [0, 1, 2, 3, 4, 5], 8)
end

def test_resize_to_simple_full
id = Time.now.strftime('%H:%M:%S.%N')
window = ::Semian::ThreadSafe::SlidingWindow.new(id, max_size: 4)
window << 0 << 1 << 2 << 3
assert_sliding_window(window, [0, 1, 2, 3], 4)
window.resize_to(8)
assert_sliding_window(window, [0, 1, 2, 3], 8)
window << 4 << 5
assert_sliding_window(window, [0, 1, 2, 3, 4, 5], 8)
end

def test_resize_to_simple_floating
id = Time.now.strftime('%H:%M:%S.%N')
window = ::Semian::ThreadSafe::SlidingWindow.new(id, max_size: 4)
window << 0 << 1 << 2 << 3
assert_sliding_window(window, [0, 1, 2, 3], 4)
window.reject! { |val| val < 2 }
assert_sliding_window(window, [2, 3], 4)
window.resize_to(8)
assert_sliding_window(window, [2, 3], 8)
window << 4 << 5
assert_sliding_window(window, [2, 3, 4, 5], 8)
end

def test_resize_to_hard
id = Time.now.strftime('%H:%M:%S.%N')
window = ::Semian::ThreadSafe::SlidingWindow.new(id, max_size: 4)
window << 0 << 1 << 2 << 3 << 4 << 5
assert_sliding_window(window, [2, 3, 4, 5], 4)
window.resize_to(8)
assert_sliding_window(window, [2, 3, 4, 5], 8)
window << 6 << 7
assert_sliding_window(window, [2, 3, 4, 5, 6, 7], 8)
window << 8 << 9
assert_sliding_window(window, [2, 3, 4, 5, 6, 7, 8, 9], 8)
end

def test_resize_to_shrink_simple
id = Time.now.strftime('%H:%M:%S.%N')
window = ::Semian::ThreadSafe::SlidingWindow.new(id, max_size: 4)
window << 0 << 1
assert_sliding_window(window, [0, 1], 4)
window.resize_to(2)
assert_sliding_window(window, [0, 1], 2)
end

def test_resize_to_shrink_simple_full
id = Time.now.strftime('%H:%M:%S.%N')
window = ::Semian::ThreadSafe::SlidingWindow.new(id, max_size: 4)
window << 0 << 1 << 2 << 3
assert_sliding_window(window, [0, 1, 2, 3], 4)
window.resize_to(2)
assert_sliding_window(window, [0, 1], 2)
end

def test_resize_to_shrink_hard
id = Time.now.strftime('%H:%M:%S.%N')
window = ::Semian::ThreadSafe::SlidingWindow.new(id, max_size: 4)
window << 0 << 1 << 2 << 3 << 4 << 5
assert_sliding_window(window, [2, 3, 4, 5], 4)
window.resize_to(2)
assert_sliding_window(window, [2, 3], 2)
end

def test_resize_to_shrink_all_index
4.times do |offset|
id = Time.now.strftime('%H:%M:%S.%N-#{offset}')
window = ::Semian::ThreadSafe::SlidingWindow.new(id, max_size: 4)
offset.times { window << offset }
window << 0 << 1 << 2 << 3
assert_sliding_window(window, [0, 1, 2, 3], 4)
window.resize_to(2)
assert_sliding_window(window, [0, 1], 2)
end
end

def test_resize_to_grow_all_index
4.times do |offset|
id = Time.now.strftime('%H:%M:%S.%N-#{offset}')
window = ::Semian::ThreadSafe::SlidingWindow.new(id, max_size: 4)
offset.times { window << offset }
window << 0 << 1 << 2 << 3
assert_sliding_window(window, [0, 1, 2, 3], 4)
window.resize_to(8)
assert_sliding_window(window, [0, 1, 2, 3], 8)
window << 4 << 5 << 6 << 7
assert_sliding_window(window, [0, 1, 2, 3, 4, 5, 6, 7], 8)
window << 8 << 9
assert_sliding_window(window, [2, 3, 4, 5, 6, 7, 8, 9], 8)
end
end

private

def assert_sliding_window(sliding_window, array, max_size)
assert_equal(array, sliding_window.values)
assert_equal(max_size, sliding_window.max_size)
assert_equal(array, sliding_window.values, "Window contents were different")
assert_equal(max_size, sliding_window.max_size, "Window max_size was not equal")
end
end

0 comments on commit 1a46fb6

Please sign in to comment.