Skip to content

Commit

Permalink
Basic implementation of Readers-writer lock (Concurrent::RWLock)
Browse files Browse the repository at this point in the history
  • Loading branch information
64kramsystem committed Nov 1, 2017
1 parent a89ff2f commit c501cdb
Show file tree
Hide file tree
Showing 3 changed files with 254 additions and 8 deletions.
167 changes: 167 additions & 0 deletions vm/concurrent_rw_lock.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package vm

import (
"sync"

"github.com/goby-lang/goby/vm/errors"
)

// ConcurrentRWLockObject is a Readers-Writer Lock (readers can concurrently put a lock, while a
// writer requires exclusive access).
//
// The implementation internally uses Go's `sync.RWLock` type.
//
// ```ruby
// require 'concurrent/rw_lock'
// lock = Concurrent::RWLock.new
// lock.with_read_lock do
// # critical section
// end
// lock.with_write_lock do
// # critical section
// end
// ```
//
type ConcurrentRWLockObject struct {
*baseObj
mutex sync.RWMutex
}

// Class methods --------------------------------------------------------
func builtinConcurrentRWLockClassMethods() []*BuiltinMethodObject {
return []*BuiltinMethodObject{
{
Name: "new",
Fn: func(receiver Object, sourceLine int) builtinMethodBody {
return func(t *thread, args []Object, blockFrame *normalCallFrame) Object {
if len(args) != 0 {
return t.vm.initErrorObject(errors.ArgumentError, sourceLine, "Expect 0 arguments, got %d", len(args))
}

return t.vm.initConcurrentRWLockObject()
}
},
},
}
}

// Instance methods -----------------------------------------------------
func builtinConcurrentRWLockInstanceMethods() []*BuiltinMethodObject {
return []*BuiltinMethodObject{
{
// Executes the block with a read lock.
// The lock is freed upon exiting the block.
//
// ```Ruby
// lock = Concurrent::RWLock.new
// lock.with_read_lock do
// # critical section
// end
//
// @return [Object] the yielded value of the block.
// ```
Name: "with_read_lock",
Fn: func(receiver Object, sourceLine int) builtinMethodBody {
return func(t *thread, args []Object, blockFrame *normalCallFrame) Object {
if blockFrame == nil {
return t.vm.initErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat)
}

if len(args) != 0 {
t.callFrameStack.pop()

return t.vm.initErrorObject(errors.ArgumentError, sourceLine, "Expected 0 arguments. got: %d", len(args))
}

lockObject := receiver.(*ConcurrentRWLockObject)

lockObject.mutex.RLock();

blockReturnValue := t.builtinMethodYield(blockFrame).Target

lockObject.mutex.RUnlock();

return blockReturnValue
}
},
},
{
// Executes the block with a write lock.
// The lock is freed upon exiting the block.
//
// ```Ruby
// lock = Concurrent::RWLock.new
// lock.with_write_lock do
// # critical section
// end
//
// @return [Object] the yielded value of the block.
// ```
Name: "with_write_lock",
Fn: func(receiver Object, sourceLine int) builtinMethodBody {
return func(t *thread, args []Object, blockFrame *normalCallFrame) Object {
if blockFrame == nil {
return t.vm.initErrorObject(errors.InternalError, sourceLine, errors.CantYieldWithoutBlockFormat)
}

if len(args) != 0 {
t.callFrameStack.pop()

return t.vm.initErrorObject(errors.ArgumentError, sourceLine, "Expected 0 arguments. got: %d", len(args))
}

lockObject := receiver.(*ConcurrentRWLockObject)

lockObject.mutex.Lock();

blockReturnValue := t.builtinMethodYield(blockFrame).Target

lockObject.mutex.Unlock();

return blockReturnValue
}
},
},
}
}

// Internal functions ===================================================

// Functions for initialization -----------------------------------------

func (vm *VM) initConcurrentRWLockObject() *ConcurrentRWLockObject {
concurrentModule := vm.loadConstant("Concurrent", true)
lockClass := concurrentModule.getClassConstant("RWLock")

return &ConcurrentRWLockObject{
baseObj: &baseObj{class: lockClass},
mutex: sync.RWMutex{},
}
}

func initConcurrentRWLockClass(vm *VM) {
concurrentModule := vm.loadConstant("Concurrent", true)
lockClass := vm.initializeClass("RWLock", false)

lockClass.setBuiltinMethods(builtinConcurrentRWLockInstanceMethods(), false)
lockClass.setBuiltinMethods(builtinConcurrentRWLockClassMethods(), true)

concurrentModule.setClassConstant(lockClass)
}

// Polymorphic helper functions -----------------------------------------

// Value returns the object
func (lock *ConcurrentRWLockObject) Value() interface{} {
return lock.mutex
}

// toString returns the object's name as the string format
func (lock *ConcurrentRWLockObject) toString() string {
return "<Instance of: " + lock.class.Name + ">"
}

// toJSON just delegates to toString
func (lock *ConcurrentRWLockObject) toJSON() string {
return lock.toString()
}
78 changes: 78 additions & 0 deletions vm/concurrent_rw_lock_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package vm

import (
"testing"
)

// This test in isolation doesn't prove that the read lock is working, as it
// would pass without locks. In order to be meaningful, it requires a context
// (in this case the UT `TestRWLockWithReadLockReadBlocksWrite`, which proves
// that the read lock itself works.
//
func TestRWLockWithReadLockSharedRead(t *testing.T) {
code := `
require 'concurrent/rw_lock'
lock = Concurrent::RWLock.new
finish_message = nil
thread do
lock.with_read_lock do
sleep 2
finish_message ||= "thread_1!"
end
end
thread do
sleep 1
lock.with_read_lock do
finish_message ||= "thread_2!"
end
end
sleep 3
finish_message
`

expected := "thread_2!"

v := initTestVM()
evaluated := v.testEval(t, code, getFilename())
testStringObject(t, i, evaluated, expected)
v.checkCFP(t, i, 0)
v.checkSP(t, i, 1)
}

func TestRWLockWithReadLockReadBlocksWrite(t *testing.T) {
code := `
require 'concurrent/rw_lock'
lock = Concurrent::RWLock.new
finish_message = nil
thread do
lock.with_read_lock do
sleep 2
finish_message ||= "thread_1!"
end
end
thread do
sleep 1
lock.with_write_lock do
finish_message ||= "thread_2!"
end
end
sleep 3
finish_message
`

expected := "thread_1!"

v := initTestVM()
evaluated := v.testEval(t, code, getFilename())
testStringObject(t, i, evaluated, expected)
v.checkCFP(t, i, 0)
v.checkSP(t, i, 1)
}
17 changes: 9 additions & 8 deletions vm/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,15 @@ type filename = string
type errorMessage = string

var standardLibraries = map[string]func(*VM){
"net/http": initHTTPClass,
"net/simple_server": initSimpleServerClass,
"uri": initURIClass,
"db": initDBClass,
"plugin": initPluginClass,
"json": initJSONClass,
"concurrent/array": initConcurrentArrayClass,
"concurrent/hash": initConcurrentHashClass,
"net/http": initHTTPClass,
"net/simple_server": initSimpleServerClass,
"uri": initURIClass,
"db": initDBClass,
"plugin": initPluginClass,
"json": initJSONClass,
"concurrent/array": initConcurrentArrayClass,
"concurrent/hash": initConcurrentHashClass,
"concurrent/rw_lock": initConcurrentRWLockClass,
}

// VM represents a stack based virtual machine.
Expand Down

0 comments on commit c501cdb

Please sign in to comment.