diff --git a/.gitignore b/.gitignore index 8c960ec..97e6a6f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.jl.cov *.jl.*.cov *.jl.mem +/Manifest.toml diff --git a/.travis.yml b/.travis.yml index 1121e9a..398008b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,13 +3,22 @@ language: julia os: - linux - osx + - windows julia: - - 0.4 + - 1.0 + - 1.1 + - 1.2 + - 1.3 - nightly +matrix: + allow_failures: + - julia: nightly + fast_finish: true notifications: email: false -script: - - if [[ -a .git/shallow ]]; then git fetch --unshallow; fi - - julia -e 'Pkg.clone(pwd()); Pkg.build("ReadWriteLocks"); Pkg.test("ReadWriteLocks"; coverage=true)' after_success: - - julia -e 'cd(Pkg.dir("ReadWriteLocks")); Pkg.add("Coverage"); using Coverage; Codecov.submit(process_folder())' + - julia -e ' + using Pkg; + Pkg.add("Coverage"); + using Coverage; + Codecov.submit(process_folder())' diff --git a/Project.toml b/Project.toml new file mode 100644 index 0000000..cbafd38 --- /dev/null +++ b/Project.toml @@ -0,0 +1,13 @@ +name = "ReadWriteLocks" +uuid = "c9a1755d-84d0-58e0-8f48-2f3129b1cb7a" +authors = "Invenia Technical Computing" +version = "0.1.0" + +[compat] +julia = "1" + +[extras] +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[targets] +test = ["Test"] diff --git a/REQUIRE b/REQUIRE deleted file mode 100644 index d5d6467..0000000 --- a/REQUIRE +++ /dev/null @@ -1 +0,0 @@ -julia 0.4 diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 26612de..0000000 --- a/appveyor.yml +++ /dev/null @@ -1,34 +0,0 @@ -environment: - matrix: - - JULIAVERSION: "julialang/bin/winnt/x86/0.4/julia-0.4-latest-win32.exe" - - JULIAVERSION: "julialang/bin/winnt/x64/0.4/julia-0.4-latest-win64.exe" - - JULIAVERSION: "julianightlies/bin/winnt/x86/julia-latest-win32.exe" - - JULIAVERSION: "julianightlies/bin/winnt/x64/julia-latest-win64.exe" - -branches: - only: - - master - - /release-.*/ - -notifications: - - provider: Email - on_build_success: false - on_build_failure: false - on_build_status_changed: false - -install: -# Download most recent Julia Windows binary - - ps: (new-object net.webclient).DownloadFile( - $("http://s3.amazonaws.com/"+$env:JULIAVERSION), - "C:\projects\julia-binary.exe") -# Run installer silently, output to C:\projects\julia - - C:\projects\julia-binary.exe /S /D=C:\projects\julia - -build_script: -# Need to convert from shallow to complete for Pkg.clone to work - - IF EXIST .git\shallow (git fetch --unshallow) - - C:\projects\julia\bin\julia -e "versioninfo(); - Pkg.clone(pwd(), \"ReadWriteLocks\"); Pkg.build(\"ReadWriteLocks\")" - -test_script: - - C:\projects\julia\bin\julia --check-bounds=yes -e "Pkg.test(\"ReadWriteLocks\")" diff --git a/src/ReadWriteLocks.jl b/src/ReadWriteLocks.jl index 1a18ac3..b1d0868 100644 --- a/src/ReadWriteLocks.jl +++ b/src/ReadWriteLocks.jl @@ -1,42 +1,39 @@ module ReadWriteLocks -export ReadWriteLock, read_lock, write_lock, lock!, unlock! - -if isdefined(Base, :lock!) - import Base: lock! -end - -if isdefined(Base, :unlock!) - import Base: unlock! +using Base: lock, unlock +if VERSION < v"1.2.0-DEV.28" + using Base.Threads: AbstractLock +else + using Base: AbstractLock end -if !isdefined(Base, :Mutex) - typealias Mutex ReentrantLock - - lock!(x::Mutex) = lock(x) - unlock!(x::Mutex) = unlock(x) -end - -abstract _Lock +export ReadWriteLock, read_lock, write_lock, lock!, unlock! -immutable ReadLock{T<:_Lock} +struct ReadLock{T<:AbstractLock} rwlock::T end -immutable WriteLock{T<:_Lock} +struct WriteLock{T<:AbstractLock} rwlock::T end -type ReadWriteLock <: _Lock +# Need Julia VERSION > v"1.2.0-DEV.28` to have `ReentrantLock <: AbstractLock` +LockTypes = Union{AbstractLock, ReentrantLock} +mutable struct ReadWriteLock{L<:LockTypes} <: AbstractLock readers::Int writer::Bool - lock::Mutex # reentrant mutex + lock::L # reentrant mutex condition::Condition read_lock::ReadLock write_lock::WriteLock - function ReadWriteLock() - rwlock = new(false, 0, Mutex(), Condition()) + function ReadWriteLock( + readers::Int=0, + writer::Bool=false, + lock::L=ReentrantLock(), + condition::Condition=Condition(), + ) where L <: LockTypes + rwlock = new{L}(readers, writer, lock, condition) rwlock.read_lock = ReadLock(rwlock) rwlock.write_lock = WriteLock(rwlock) @@ -49,7 +46,7 @@ write_lock(rwlock::ReadWriteLock) = rwlock.write_lock function lock!(read_lock::ReadLock) rwlock = read_lock.rwlock - lock!(rwlock.lock) + lock(rwlock.lock) try while rwlock.writer @@ -58,7 +55,7 @@ function lock!(read_lock::ReadLock) rwlock.readers += 1 finally - unlock!(rwlock.lock) + unlock(rwlock.lock) end return nothing @@ -66,7 +63,7 @@ end function unlock!(read_lock::ReadLock) rwlock = read_lock.rwlock - lock!(rwlock.lock) + lock(rwlock.lock) try rwlock.readers -= 1 @@ -74,7 +71,7 @@ function unlock!(read_lock::ReadLock) notify(rwlock.condition; all=true) end finally - unlock!(rwlock.lock) + unlock(rwlock.lock) end return nothing @@ -82,7 +79,7 @@ end function lock!(write_lock::WriteLock) rwlock = write_lock.rwlock - lock!(rwlock.lock) + lock(rwlock.lock) try while rwlock.readers > 0 || rwlock.writer @@ -91,7 +88,7 @@ function lock!(write_lock::WriteLock) rwlock.writer = true finally - unlock!(rwlock.lock) + unlock(rwlock.lock) end return nothing @@ -99,13 +96,13 @@ end function unlock!(write_lock::WriteLock) rwlock = write_lock.rwlock - lock!(rwlock.lock) + lock(rwlock.lock) try rwlock.writer = false notify(rwlock.condition; all=true) finally - unlock!(rwlock.lock) + unlock(rwlock.lock) end return nothing diff --git a/test/REQUIRE b/test/REQUIRE deleted file mode 100644 index bc3e234..0000000 --- a/test/REQUIRE +++ /dev/null @@ -1 +0,0 @@ -FactCheck diff --git a/test/runtests.jl b/test/runtests.jl index 962b460..89a27f6 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,2 +1,191 @@ -# write your own tests here -include("singlethreaded.jl") +using ReadWriteLocks +using Test + +@testset "ReadWriteLock" begin + +@testset "Constructor" begin + @test ReadWriteLock() isa ReadWriteLock{ReentrantLock} + @test ReadWriteLock(0, false, Threads.Mutex()) isa ReadWriteLock{Threads.Mutex} +end + +@testset "Single-threaded tests" begin + @testset "Initialization" begin + rwlock = ReadWriteLock() + + @test rwlock.read_lock.rwlock == rwlock + @test rwlock.write_lock.rwlock == rwlock + @test rwlock.readers == 0 + @test rwlock.writer == false + @test read_lock(rwlock) == rwlock.read_lock + @test write_lock(rwlock) == rwlock.write_lock + end +end # Single-threaded + +@testset "Two-threaded tests" begin + @testset "Read locks" begin + NUM_LOCKS = 10 + + @testset "$NUM_LOCKS locks" begin + rwlock = ReadWriteLock() + rlock = read_lock(rwlock) + + @async begin + @test rwlock.writer == false + @test rwlock.readers == 0 + for i = 1:NUM_LOCKS + @test lock!(rlock) == nothing + # lock(rlock) + @test rwlock.readers == i + end + end + + sleep(1) + + @test rwlock.readers == NUM_LOCKS + + @async begin + @test rwlock.writer == false + @test rwlock.readers == NUM_LOCKS + for i in NUM_LOCKS:-1:1 + @test unlock!(rlock) == nothing + @test rwlock.readers == i - 1 + end + @test rwlock.readers == 0 + end + + sleep(1) + + @test rwlock.readers == 0 + end + end + + @testset "Write locks" begin + @testset "two locks" begin + rwlock = ReadWriteLock() + wlock = write_lock(rwlock) + + c = Channel{Symbol}(1) + put!(c, :pretest) + + @async begin + @test rwlock.writer == false + @test rwlock.readers == 0 + @test lock!(wlock) == nothing + @test rwlock.writer == true + @test rwlock.readers == 0 + @test take!(c) == :pretest + put!(c, :prelock) + @test lock!(wlock) == nothing + + # this code should never be reached + @test take!(c) == :prelock + put!(c, :postlock) + end + + sleep(1) + + @test take!(c) == :prelock + put!(c, :posttest) + + @test rwlock.writer == true + @test rwlock.readers == 0 + end + + @testset "unlock" begin + rwlock = ReadWriteLock() + wlock = write_lock(rwlock) + + c = Channel{Symbol}(1) + put!(c, :pretest) + + @async begin + @test rwlock.writer == false + @test rwlock.readers == 0 + @test lock!(wlock) == nothing + @test rwlock.writer == true + @test rwlock.readers == 0 + @test take!(c) == :pretest + put!(c, :preunlock) + @test unlock!(wlock) == nothing + @test take!(c) == :preunlock + put!(c, :postunlock) + end + + sleep(1) + + @test take!(c) == :postunlock + put!(c, :posttest) + + @test rwlock.writer == false + @test rwlock.readers == 0 + end + end + + @testset "read and write locks" begin + @testset "write then read" begin + rwlock = ReadWriteLock() + wlock = write_lock(rwlock) + rlock = read_lock(rwlock) + + c = Channel{Symbol}(1) + put!(c, :pretest) + + @async begin + @test rwlock.writer == false + @test rwlock.readers == 0 + @test lock!(wlock) == nothing + @test rwlock.writer == true + @test rwlock.readers == 0 + @test take!(c) == :pretest + put!(c, :prelock) + @test lock!(rlock) == nothing + + # this code should never be reached + @test take!(c) == :prelock + put!(c, :postlock) + end + + sleep(1) + + @test take!(c) == :prelock + put!(c, :posttest) + + @test rwlock.writer == true + @test rwlock.readers == 0 + end + + @testset "read then write" begin + rwlock = ReadWriteLock() + wlock = write_lock(rwlock) + rlock = read_lock(rwlock) + + c = Channel{Symbol}(1) + put!(c, :pretest) + + @async begin + @test rwlock.writer == false + @test rwlock.readers == 0 + @test lock!(rlock) == nothing + @test rwlock.writer == false + @test rwlock.readers == 1 + @test take!(c) == :pretest + put!(c, :prelock) + @test lock!(wlock) == nothing + + # this code should never be reached + @test take!(c) == :prelock + put!(c, :postlock) + end + + sleep(1) + + @test take!(c) == :prelock + put!(c, :posttest) + + @test rwlock.writer == false + @test rwlock.readers == 1 + end + end +end # Two-threaded + +end # ReadWriteLocks diff --git a/test/singlethreaded.jl b/test/singlethreaded.jl deleted file mode 100644 index 5564f03..0000000 --- a/test/singlethreaded.jl +++ /dev/null @@ -1,182 +0,0 @@ -using FactCheck -using ReadWriteLocks - -facts("Single-threaded tests") do - context("Initialization") do - rwlock = ReadWriteLock() - - @fact rwlock.read_lock.rwlock --> rwlock - @fact rwlock.write_lock.rwlock --> rwlock - @fact rwlock.readers --> 0 - @fact rwlock.writer --> false - @fact read_lock(rwlock) --> rwlock.read_lock - @fact write_lock(rwlock) --> rwlock.write_lock - end -end - -facts("Two-threaded tests") do - context("Read locks") do - NUM_LOCKS = 10 - - context("$NUM_LOCKS locks") do - rwlock = ReadWriteLock() - rlock = read_lock(rwlock) - - @async begin - @fact rwlock.writer --> false - @fact rwlock.readers --> 0 - for i = 1:NUM_LOCKS - @fact lock!(rlock) --> nothing - @fact rwlock.readers --> i - end - end - - sleep(1) - - @fact rwlock.readers --> NUM_LOCKS - - @async begin - @fact rwlock.writer --> false - @fact rwlock.readers --> NUM_LOCKS - for i = NUM_LOCKS:-1:1 - @fact unlock!(rlock) --> nothing - @fact rwlock.readers --> i - 1 - end - @fact rwlock.readers --> 0 - end - - sleep(1) - - @fact rwlock.readers --> 0 - end - end - - context("Write locks") do - context("two locks") do - rwlock = ReadWriteLock() - wlock = write_lock(rwlock) - - c = Channel{Symbol}(1) - put!(c, :pretest) - - @async begin - @fact rwlock.writer --> false - @fact rwlock.readers --> 0 - @fact lock!(wlock) --> nothing - @fact rwlock.writer --> true - @fact rwlock.readers --> 0 - @fact take!(c) --> :pretest - put!(c, :prelock) - @fact lock!(wlock) --> nothing - - # this code should never be reached - @fact take!(c) --> :prelock - put!(c, :postlock) - end - - sleep(1) - - @fact take!(c) --> :prelock - put!(c, :posttest) - - @fact rwlock.writer --> true - @fact rwlock.readers --> 0 - end - - context("unlock") do - rwlock = ReadWriteLock() - wlock = write_lock(rwlock) - - c = Channel{Symbol}(1) - put!(c, :pretest) - - @async begin - @fact rwlock.writer --> false - @fact rwlock.readers --> 0 - @fact lock!(wlock) --> nothing - @fact rwlock.writer --> true - @fact rwlock.readers --> 0 - @fact take!(c) --> :pretest - put!(c, :preunlock) - @fact unlock!(wlock) --> nothing - @fact take!(c) --> :preunlock - put!(c, :postunlock) - end - - sleep(1) - - @fact take!(c) --> :postunlock - put!(c, :posttest) - - @fact rwlock.writer --> false - @fact rwlock.readers --> 0 - end - end - - context("read and write locks") do - context("write then read") do - rwlock = ReadWriteLock() - wlock = write_lock(rwlock) - rlock = read_lock(rwlock) - - c = Channel{Symbol}(1) - put!(c, :pretest) - - @async begin - @fact rwlock.writer --> false - @fact rwlock.readers --> 0 - @fact lock!(wlock) --> nothing - @fact rwlock.writer --> true - @fact rwlock.readers --> 0 - @fact take!(c) --> :pretest - put!(c, :prelock) - @fact lock!(rlock) --> nothing - - # this code should never be reached - @fact take!(c) --> :prelock - put!(c, :postlock) - end - - sleep(1) - - @fact take!(c) --> :prelock - put!(c, :posttest) - - @fact rwlock.writer --> true - @fact rwlock.readers --> 0 - end - - context("read then write") do - rwlock = ReadWriteLock() - wlock = write_lock(rwlock) - rlock = read_lock(rwlock) - - c = Channel{Symbol}(1) - put!(c, :pretest) - - @async begin - @fact rwlock.writer --> false - @fact rwlock.readers --> 0 - @fact lock!(rlock) --> nothing - @fact rwlock.writer --> false - @fact rwlock.readers --> 1 - @fact take!(c) --> :pretest - put!(c, :prelock) - @fact lock!(wlock) --> nothing - - # this code should never be reached - @fact take!(c) --> :prelock - put!(c, :postlock) - end - - sleep(1) - - @fact take!(c) --> :prelock - put!(c, :posttest) - - @fact rwlock.writer --> false - @fact rwlock.readers --> 1 - end - end - -end