Skip to content
Open
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
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,15 @@ doc/build/
python/ndtypes/ndtypes.h
*.egg-info
__pycache__

# Ignore various ruby generated build and test files
ruby/tmp/
ruby/pkg/
*.gem
ruby/Gemfile.lock
ruby/ext/ndtypes/
ruby/ext/ruby_ndtypes/share/
ruby/ext/ruby_ndtypes/include/
GPATH
GRTAGS
GTAGS
1 change: 1 addition & 0 deletions python/ndtypes/_ndtypes.c
Original file line number Diff line number Diff line change
Expand Up @@ -1163,6 +1163,7 @@ ndtype_typedef(PyObject *mod UNUSED, PyObject *args, PyObject *kwds)

t = ndt_from_string(ctype, &ctx);
if (t == NULL) {

return seterr(&ctx);
}

Expand Down
62 changes: 62 additions & 0 deletions ruby/CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Developer notes

## Contributing to ndtypes Ruby wrapper

If you're interested in contributing to the ndtypes Ruby wrapper, follow the following
steps for getting started:

* Clone the ndtypes repo: `git clone https://github.com/plures/ndtypes.git`.
* Switch to the `ruby-wrapper` branch: `git checkout ruby-wrapper`.
* Run `bundle install`.
* Run `rake develop`. This will build ndtypes with the latest working master branch
of libndtypes.
* Run `rspec` for running the tests.

## Interfacing with Ruby's GC

### Background

Ruby uses a mark-and-sweep GC that scans the entire Ruby interpreter stack for
objects that have gone out of scope and can be freed from memory. It does not
offer any of the reference counting mechanism that the Python GC offers.

While both approaches have their pros and cons, in the context of the ndtypes
wrapper, it becomes risky to have 'internal' Ruby objects that are only visible
to the C API and are shared between multiple user-facing Ruby objecs. If one of
the user-facing objects goes out of scope there is a possibilty that the GC will
clean up the internal object that is shared between multiple user-facing objects
(some of which might still be in use) and that will lead to segfaults.

To avoid such a situation, in ndtypes we use a 'global GC guard' (inspired by @mrkn's
[pycall.rb](https://github.com/mrkn/pycall.rb) gem) that stores the reference to the internal objects in a global Hash
so that they don't go out of scope. When a user-facing object needs to be freed, we remove
the reference to the user-facing object and its corresponding internal object from the
global Hash.

### Details

More concretely, the `NdtObject` struct houses a `VALUE` object called `rbuf`. The struct
has the following definition:
```
typedef struct {
VALUE rbuf; /* resource buffer */
ndt_t *ndt; /* type */
} NdtObject;
```
In the above, the `rbuf` is a Ruby object that contains a struct of type `ResourceBufferObject`.
This is the internal object that need to be shared among multiple user-facing `NDTypes` objects.

The `gc_guard.c` file contains functions that help us interface with a global hash called
`__gc_guard_table` that is present under the `NDTypes::GCGuard` module as a instance variable
on the module object.

### Impact on contributor

Whenever you allocate an `NDTypes` object you call the `gc_guard_register` function and pass
it the pointer of the `NdtObject` struct that you have allocated along with the `rbuf` object.

When an `NDTypes` objects needs to be freed (for example by the GC using the `NDTypes_dfree`
function), you must call the `gc_guard_unregister` function that will remove the reference
to the `rbuf` object from the global Hash.

Forgetting to call above procedures can lead to hard-to-trace GC errors.
2 changes: 2 additions & 0 deletions ruby/Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
source 'https://rubygems.org'
gemspec
Empty file added ruby/History.md
Empty file.
19 changes: 19 additions & 0 deletions ruby/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# ndtypes

Ruby wrapper over the libndtypes library.

# Installation

Run `gem install ndtypes --pre` to install on your machine.

# Usage



# Tests

Run `rspec`.

# Contributing

Read the `CONTRIBUTING.md` file to know how to contribute to NDTypes.
133 changes: 133 additions & 0 deletions ruby/Rakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
require 'rake'
require 'rake/extensiontask'
require 'rake/testtask'
require 'rspec/core/rake_task'
require 'bundler/gem_tasks'
require 'fileutils'
require "ndtypes/version.rb"

gemspec = eval(IO.read("ndtypes.gemspec"))

ext_name = "ruby_ndtypes"
Rake::ExtensionTask.new(ext_name, gemspec) do |ext|
ext.ext_dir = "ext/#{ext_name}"
ext.source_pattern = "**/*.{c, h}"
end

def run *cmd
sh(cmd.join(" "))
end

BASEDIR = Pathname( __FILE__ ).dirname.relative_path_from( Pathname.pwd )
SPECDIR = BASEDIR + 'spec'

VALGRIND_OPTIONS = [
"--tool=memcheck",
#"--leak-check=yes",
"--num-callers=15",
#"--error-limit=no",
"--partial-loads-ok=yes",
"--undef-value-errors=no" #,
#"--dsymutil=yes"
]

CALLGRIND_OPTIONS = [
"--tool=callgrind",
"--dump-instr=yes",
"--simulate-cache=yes",
"--collect-jumps=yes"
]

VALGRIND_MEMORYFILL_OPTIONS = [
"--freelist-vol=100000000",
"--malloc-fill=6D",
"--free-fill=66 ",
]

GDB_OPTIONS = []

namespace :spec do
# partial-loads-ok and undef-value-errors necessary to ignore
# spurious (and eminently ignorable) warnings from the ruby
# interpreter

RSPEC_CMD = [ 'ruby', '-S', 'rspec', '-Ilib:ext', SPECDIR.to_s ]

desc "Run specs under GDB."
task :gdb => [ :compile ] do |task|
cmd = [ 'gdb' ] + GDB_OPTIONS
cmd += [ '--args' ]
cmd += RSPEC_CMD
run( *cmd )
end

desc "Run specs under cgdb."
task :cgdb => [ :compile ] do |task|
cmd = [ 'cgdb' ] + GDB_OPTIONS
cmd += [ '--args' ]
cmd += RSPEC_CMD
run( *cmd )
end

desc "Run specs under Valgrind."
task :valgrind => [ :compile ] do |task|
cmd = [ 'valgrind' ] + VALGRIND_OPTIONS
cmd += RSPEC_CMD
run( *cmd )
end

desc "Run specs under Callgrind."
task :callgrind => [ :compile ] do |task|
cmd = [ 'valgrind' ] + CALLGRIND_OPTIONS
cmd += RSPEC_CMD
run( *cmd )
end
end

LEAKCHECK_CMD = [ 'ruby', '-Ilib:ext', "#{SPECDIR}/leakcheck.rb" ]

desc "Run leakcheck script."
task :leakcheck => [ :compile ] do |task|
cmd = [ 'valgrind' ] + VALGRIND_OPTIONS
cmd += LEAKCHECK_CMD
run( *cmd )
end


Rake::TestTask.new(:test) do |t|
t.libs << "test"
t.libs << "lib"
t.test_files = FileList['test/**/test_*.rb']
end

task :clobber do |task|
[
"ext/#{ext_name}/include",
"ext/#{ext_name}/share",
"ext/#{ext_name}/lib",
].each do |f|
puts "deleting folder #{f}..."
FileUtils.rm_rf(f)
end

Dir.chdir("ext/#{ext_name}/ndtypes/libndtypes/") do
system("make clean")
end
end

task :develop do |task|
ext_ndtypes = "ext/ruby_ndtypes/ndtypes"
puts "deleting previously created #{ext_ndtypes} directory..."
FileUtils.rm_rf(ext_ndtypes)
Dir.mkdir(ext_ndtypes)

puts "cloning ndtypes repo into ext/ folder..."
system("git clone https://github.com/plures/ndtypes #{ext_ndtypes}")

Dir.chdir(ext_ndtypes) do
system("git checkout #{NDTypes::COMMIT}")
end

puts "building gem with rake build..."
system("rake build")
end
62 changes: 62 additions & 0 deletions ruby/ext/ruby_ndtypes/extconf.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
require 'mkmf'

def windows?
(/cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM) != nil
end

def mac?
(/darwin/ =~ RUBY_PLATFORM) != nil
end

def unix?
!windows?
end

puts "compiling libndtypes on your machine..."
Dir.chdir(File.join(File.dirname(__FILE__) + "/ndtypes")) do
if unix?
["libndtypes", "libndtypes/compat", "libndtypes/serialize"].each do |f|
Dir.chdir(f) do
Dir.mkdir(".objs") unless Dir.exists? ".objs"
end
end

system("./configure --prefix=#{File.expand_path("../")} --with-docs=no")
system("make")
system("make install")
elsif windows?
raise NotImplementedError, "need to specify build instructions for windows."
end
end

$INSTALLFILES = [
["ruby_ndtypes.h", "$(archdir)"],
["ndtypes.h", "$(archdir)"]
]

binaries = File.expand_path(File.join(File.dirname(__FILE__) + "/lib/"))
headers = File.expand_path(File.join(File.dirname(__FILE__) + "/include/"))
$LOAD_PATH << File.expand_path(binaries)
append_ldflags("-Wl,-rpath #{binaries}")

["ndtypes"].each do |lib|
find_library(lib, nil, binaries)
end

["ndtypes.h"].each do |header|
find_header(header, headers)
have_header(header)
end

FileUtils.copy_file File.expand_path(File.join(File.dirname(__FILE__) +
"/ruby_ndtypes.h")),
"#{headers}/ruby_ndtypes.h"

dir_config("ndtypes", [headers], [binaries])

basenames = %w{gc_guard ruby_ndtypes}
$objs = basenames.map { |b| "#{b}.o" }
$srcs = basenames.map { |b| "#{b}.c" }

$CFLAGS += " -O0 -g "
create_makefile("ruby_ndtypes/ruby_ndtypes")
36 changes: 36 additions & 0 deletions ruby/ext/ruby_ndtypes/gc_guard.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/* Functions useful for interfacing shared rbuf objects with the Ruby GC. */
/* Author: Sameer Deshmukh (@v0dro) */
#include "ruby_ndtypes_internal.h"

#define GC_GUARD_TABLE_NAME "@__gc_guard_table"

static ID id_gc_guard_table;

/* Unregister an NDT object-rbuf pair from the GC guard. */
void
rb_ndtypes_gc_guard_unregister(NdtObject *ndt)
{
VALUE table = rb_ivar_get(mNDTypes_GCGuard, id_gc_guard_table);
rb_hash_delete(table, PTR2NUM(ndt));
}

/* Register a NDT-rbuf pair in the GC guard. */
void
rb_ndtypes_gc_guard_register(NdtObject *ndt, VALUE rbuf)
{
VALUE table = rb_ivar_get(mNDTypes_GCGuard, id_gc_guard_table);
if (table == Qnil) {
rb_raise(rb_eLoadError, "GC guard not initialized.");
}

rb_hash_aset(table, PTR2NUM(ndt), rbuf);
}

/* Initialize the global GC guard table. klass is a VALUE reprensenting NDTypes class. */
void
rb_ndtypes_init_gc_guard(void)
{
id_gc_guard_table = rb_intern(GC_GUARD_TABLE_NAME);
rb_ivar_set(mNDTypes_GCGuard, id_gc_guard_table, rb_hash_new());
}

12 changes: 12 additions & 0 deletions ruby/ext/ruby_ndtypes/gc_guard.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/* Header file containing various functions for GC guard table. */

#ifndef GC_GUARD_H
#define GC_GUARD_H

#include "ruby_ndtypes_internal.h"

void rb_ndtypes_gc_guard_unregister(NdtObject *ndt);
void rb_ndtypesgc_guard_register(NdtObject *ndt, VALUE rbuf);
void rb_ndtypes_init_gc_guard(VALUE cNDTypes);

#endif /* GC_GUARD_H */
1 change: 1 addition & 0 deletions ruby/ext/ruby_ndtypes/ndtypes
Submodule ndtypes added at 7c4cda
Loading