Skip to content

Commit 37f8378

Browse files
authored
Add some common utilities for working w/ aws libraries (#24)
1 parent fdc0150 commit 37f8378

File tree

3 files changed

+134
-1
lines changed

3 files changed

+134
-1
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "LibAwsCommon"
22
uuid = "c6e421ba-b5f8-4792-a1c4-42948de3ed9d"
3-
version = "1.2.4"
3+
version = "1.3.0"
44

55
[deps]
66
CEnum = "fa961155-64e5-5f13-b03f-caf6b980ea82"

src/LibAwsCommon.jl

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,4 +71,105 @@ function init(allocator=default_aws_allocator())
7171
return
7272
end
7373

74+
# utilities for interacting with AWS library APIs
75+
76+
# like a Ref, but for a field of a struct
77+
# many AWS APIs take a pointer to an aws struct, so this makes it convenient to to pass
78+
# a field of a wrapper struct to a library function
79+
struct FieldRef{T, S}
80+
x::T
81+
field::Symbol
82+
83+
function FieldRef(x::T, field::Symbol) where {T}
84+
@assert isconcretetype(T) && ismutabletype(T) "only fields of mutable types are supported with FieldRef"
85+
S = fieldtype(T, field)
86+
@assert isconcretetype(S) && !ismutabletype(S) "field type must be concrete and immutable for FieldRef"
87+
return new{T, S}(x, field)
88+
end
89+
end
90+
91+
function Base.unsafe_convert(P::Union{Type{Ptr{S}},Type{Ptr{Cvoid}}}, x::FieldRef{T, S}) where {T, S}
92+
return P(pointer_from_objref(x.x) + fieldoffset(T, Base.fieldindex(T, x.field)))
93+
end
94+
95+
Base.pointer(x::FieldRef{S, T}) where {S, T} = Base.unsafe_convert(Ptr{T}, x)
96+
97+
# wraps a pointer to a struct and allows get/set on fields w/o unsafe_loading the entire struct
98+
struct StructRef{T}
99+
ptr::Ptr{T}
100+
101+
function StructRef(ptr::Ptr{T}) where {T}
102+
@assert isconcretetype(T) "only concrete struct types are supported with StructRef"
103+
return new{T}(ptr)
104+
end
105+
end
106+
107+
function Base.getproperty(x::StructRef{T}, k::Symbol) where {T}
108+
S = fieldtype(T, k)
109+
@assert isconcretetype(S) && !ismutabletype(S) "field type must be concrete and immutable for StructRef"
110+
return unsafe_load(Ptr{S}(Ptr{UInt8}(getfield(x, :ptr)) + fieldoffset(T, Base.fieldindex(T, k))))
111+
end
112+
113+
function Base.setproperty!(x::StructRef{T}, k::Symbol, v) where {T}
114+
S = fieldtype(T, k)
115+
@assert isconcretetype(S) && !ismutabletype(S) "field type must be concrete and immutable for StructRef"
116+
unsafe_store!(Ptr{S}(Ptr{UInt8}(getfield(x, :ptr)) + fieldoffset(T, Base.fieldindex(T, k))), convert(S, v))
117+
return v
118+
end
119+
120+
# simple threadsafe Future impl appropriate for use with the common callback patterns in AWS libs
121+
mutable struct Future{T}
122+
const notify::Threads.Condition
123+
@atomic set::Int8 # if 0, result is undefined, 1 means result is T, 2 means result is an exception
124+
result::Union{Exception, T} # undefined initially
125+
Future{T}() where {T} = new{T}(Threads.Condition(), 0)
126+
end
127+
128+
Base.pointer(f::Future) = pointer_from_objref(f)
129+
Future(ptr::Ptr) = unsafe_pointer_to_objref(ptr)::Future
130+
Future{T}(ptr::Ptr) where {T} = unsafe_pointer_to_objref(ptr)::Future{T}
131+
132+
function Base.wait(f::Future{T}) where {T}
133+
set = @atomic f.set
134+
set == 1 && return f.result::T
135+
set == 2 && throw(f.result::Exception)
136+
lock(f.notify) # acquire barrier
137+
try
138+
set = f.set
139+
set == 1 && return f.result::T
140+
set == 2 && throw(f.result::Exception)
141+
wait(f.notify)
142+
finally
143+
unlock(f.notify) # release barrier
144+
end
145+
if f.set == 1
146+
return f.result::T
147+
else
148+
@assert isdefined(f, :result)
149+
throw(f.result::Exception)
150+
end
151+
end
152+
153+
capture(e::Exception) = CapturedException(e, Base.backtrace())
154+
155+
function Base.notify(f::Future{T}, x::Union{Exception, T}) where {T}
156+
lock(f.notify) # acquire barrier
157+
try
158+
if f.set == Int8(0)
159+
if x isa Exception
160+
set = Int8(2)
161+
f.result = x
162+
else
163+
set = Int8(1)
164+
f.result = x
165+
end
166+
@atomic :release f.set = set
167+
notify(f.notify)
168+
end
169+
finally
170+
unlock(f.notify)
171+
end
172+
nothing
173+
end
174+
74175
end

test/runtests.jl

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
using Test, Aqua, LibAwsCommon
2+
import LibAwsCommon: FieldRef, StructRef, Future
3+
4+
mutable struct Job
5+
name::String
6+
wage::Float64
7+
end
28

39
@testset "LibAwsCommon" begin
410
@testset "aqua" begin
@@ -18,4 +24,30 @@ using Test, Aqua, LibAwsCommon
1824
@test isfile(logpath) # might as well check this but we're mainly testing we don't crash
1925
end
2026
end
27+
@testset "FieldRef/StructRef" begin
28+
job = Job("plumber", 50000.0)
29+
GC.@preserve job begin
30+
@test_throws AssertionError FieldRef(job, :name)
31+
wage_field = FieldRef(job, :wage)
32+
@test pointer(wage_field) isa Ptr{Float64}
33+
@test unsafe_load(pointer(wage_field)) == 50000.0
34+
@test_throws AssertionError StructRef(Ptr{Any}(pointer_from_objref(job)))
35+
job_ref = StructRef(Ptr{Job}(pointer_from_objref(job)))
36+
@test job_ref.wage == 50000.0
37+
end
38+
end
39+
@testset "Future" begin
40+
f = Future{Int}()
41+
t = Threads.@spawn wait(f)
42+
@test !istaskdone(t)
43+
notify(f, 10)
44+
@test fetch(t) == 10
45+
@test wait(f) == 10
46+
f2 = Future{Int}()
47+
t2 = Threads.@spawn wait(f2)
48+
@test !istaskdone(t2)
49+
notify(f2, ArgumentError("Error!"))
50+
@test_throws TaskFailedException fetch(t2)
51+
@test_throws ArgumentError wait(f2)
52+
end
2153
end

0 commit comments

Comments
 (0)