diff --git a/base/exports.jl b/base/exports.jl index 0f102ff035b85..5f3dfc083e154 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -1,3 +1,4 @@ + export # Modules PCRE, @@ -45,6 +46,7 @@ export Enumerate, EnvHash, #FileDes, + FileMonitor, FileOffset, Filter, IO, @@ -60,6 +62,7 @@ export #PipeIn, #PipeOut, PipeBuffer, + PollingFileWatcher, #Port, #Ports, #ProcessExited, @@ -993,6 +996,9 @@ export eof, fd, fdio, + FDWatcher, + UV_READABLE, + UV_WRITEABLE, flush, gethostname, getipaddr, @@ -1011,6 +1017,8 @@ export nb_available, open, open_any_tcp_port, + OS_FD, + OS_SOCKET, position, read, readall, @@ -1026,9 +1034,12 @@ export serialize, skip, start_reading, + start_watching, stop_reading, start_timer, stop_timer, + poll_fd, + poll_file, takebuf_array, takebuf_string, truncate, diff --git a/base/process.jl b/base/process.jl index b592bab6e563b..08360dee2963e 100644 --- a/base/process.jl +++ b/base/process.jl @@ -165,6 +165,7 @@ function _jl_spawn(cmd::Ptr{Uint8}, argv::Ptr{Ptr{Uint8}}, loop::Ptr{Void}, pp:: c_free(proc) throw(UVError("spawn")) end + associate_julia_struct(proc,pp) return proc end diff --git a/base/stat.jl b/base/stat.jl index e514f1c7c6bea..baa264ee69fad 100644 --- a/base/stat.jl +++ b/base/stat.jl @@ -13,7 +13,7 @@ immutable Stat ctime :: Float64 end -Stat(buf::Vector{Uint8}) = Stat( +Stat(buf::Union(Vector{Uint8},Ptr{Uint8})) = Stat( uint(ccall(:jl_stat_dev, Uint32, (Ptr{Uint8},), buf)), uint(ccall(:jl_stat_ino, Uint32, (Ptr{Uint8},), buf)), uint(ccall(:jl_stat_mode, Uint32, (Ptr{Uint8},), buf)), diff --git a/base/stream.jl b/base/stream.jl index fd210025905ab..fa0550c8c2c08 100644 --- a/base/stream.jl +++ b/base/stream.jl @@ -19,6 +19,10 @@ typealias UVHandle Ptr{Void} typealias UVStream AsyncStream const _sizeof_uv_pipe = ccall(:jl_sizeof_uv_pipe_t,Int32,()) +const _sizeof_uv_poll = ccall(:jl_sizeof_uv_poll_t,Int32,()) +const _sizeof_uv_fs_poll = ccall(:jl_sizeof_uv_fs_poll_t,Csize_t,()) +const _sizeof_uv_fs_events = ccall(:jl_sizeof_uv_fs_events_t,Csize_t,()) + function eof(s::AsyncStream) start_reading(s) @@ -55,6 +59,140 @@ end show(io::IO,stream::TTY) = print(io,"TTY(",stream.open?"connected,":"disconnected,",nb_available(stream.buffer)," bytes waiting)") + +type FileMonitor + handle::Ptr{Void} + cb::Callback + function FileMonitor(cb, file) + handle = c_malloc(_sizeof_uv_fs_events) + err = ccall(:jl_fs_event_init,Int32, (Ptr{Void}, Ptr{Void}, Ptr{Uint8}, Int32), eventloop(),handle,file,0) + if err == -1 + c_free(handle) + throw(UVError("FileMonitor")) + end + this = new(handle,cb) + associate_julia_struct(handle,this) + finalizer(this,close) + this + end + FileMonitor(file) = FileMonitor(false,file) +end + +close(t::FileMonitor) = ccall(:jl_close_uv,Void,(Ptr{Void},),t.handle) + +const UV_READABLE = 1 +const UV_WRITEABLE = 2 + +#Wrapper for an OS file descriptor (on both Unix and Windows) +immutable OS_FD + fd::Int32 +end + +#Wrapper for an OS file descriptor (for Windows) +@windows_only immutable OS_SOCKET + handle::Ptr{Void} # On Windows file descriptors are HANDLE's and 64-bit on 64-bit Windows... +end + +abstract UVPollingWatcher + +type PollingFileWatcher <: UVPollingWatcher + handle::Ptr{Void} + file::ASCIIString + cb::Callback + function PollingFileWatcher(cb, file) + handle = c_malloc(_sizeof_uv_fs_poll) + err = ccall(:uv_fs_poll_init,Int32,(Ptr{Void},Ptr{Void}),eventloop(),handle) + if err == -1 + c_free(handle) + throw(UVError("PollingFileWatcher")) + end + this = new(handle, file, cb) + associate_julia_struct(handle,this) + finalizer(this,close) + this + end + PollingFileWatcher(file) = PollingFileWatcher(false,file) +end + +type FDWatcher <: UVPollingWatcher + handle::Ptr{Void} + cb::Callback + function FDWatcher(fd::OS_FD) + handle = c_malloc(_sizeof_uv_poll) + err = ccall(:uv_poll_init,Int32,(Ptr{Void},Ptr{Void},Int32),eventloop(),handle,fd.fd) + if err == -1 + c_free(handle) + throw(UVError("FDWatcher")) + end + this = new(handle,false) + associate_julia_struct(handle,this) + finalizer(this,close) + this + end + @windows_only function FDWatcher(fd::OS_SOCKET) + handle = c_malloc(_sizeof_uv_poll) + err = ccall(:uv_poll_init_socket,Int32,(Ptr{Void}, Ptr{Void}, Ptr{Void}), + eventloop(), handle, fd.handle) + if err == -1 + c_free(handle) + throw(UVError("FDWatcher")) + end + this = new(handle,false) + associate_julia_struct(handle,this) + finalizer(this,close) + this + end +end + +close(t::UVPollingWatcher) = ccall(:jl_close_uv,Void,(Ptr{Void},),t.handle) + +function start_watching(t::FDWatcher, events) + associate_julia_struct(t.handle, t) + uv_error("start_watching (FD)", + ccall(:jl_poll_start,Int32,(Ptr{Void},Int32),t.handle,events)==-1) +end +start_watching(f::Function, t::FDWatcher, events) = (t.cb = f; start_watching(t,events)) + +function start_watching(t::PollingFileWatcher, interval) + associate_julia_struct(t.handle, t) + uv_error("start_watching (File)", + ccall(:jl_fs_poll_start,Int32,(Ptr{Void},Ptr{Uint8},Uint32),t.handle,t.file,interval)==-1) +end +start_watching(f::Function, t::PollingFileWatcher, interval) = (t.cb = f;start_watching(t,interval)) + +function stop_watching(t::FDWatcher) + disassociate_julia_struct(t.handle) + uv_error("stop_watching (FD)", + ccall(:uv_poll_stop,Int32,(Ptr{Void},),t.handle)==-1) +end + +function stop_watching(t::PollingFileWatcher) + disassociate_julia_struct(t.handle) + uv_error("stop_watching (File)", + ccall(:uv_fs_poll_stop,Int32,(Ptr{Void},),t.handle)==-1) +end + +function _uv_hook_fseventscb(t::FileMonitor,filename::Ptr,events::Int32,status::Int32) + if(isa(t.cb,Function)) + # bytestring(convert(Ptr{Uint8},filename)) - seems broken at the moment - got NULL + t.cb(status, events, status) + end +end + +function _uv_hook_pollcb(t::FDWatcher,status::Int32,events::Int32) + if(isa(t.cb,Function)) + t.cb(status, events) + end +end +function _uv_hook_fspollcb(t::PollingFileWatcher,status::Int32,prev::Ptr,cur::Ptr) + if(isa(t.cb,Function)) + t.cb(status, Stat(convert(Ptr{Uint8},prev)), Stat(convert(Ptr{Uint8},cur))) + end +end + +_uv_hook_close(uv::FileMonitor) = (uv.handle = 0; nothing) +_uv_hook_close(uv::UVPollingWatcher) = (uv.handle = 0; nothing) + uvtype(::AsyncStream) = UV_STREAM uvhandle(stream::AsyncStream) = stream.handle @@ -66,10 +204,15 @@ handle(s::Ptr{Void}) = s make_stdout_stream() = _uv_tty2tty(ccall(:jl_stdout_stream, Ptr{Void}, ())) +associate_julia_struct(handle::Ptr{Void},jlobj::ANY) = + ccall(:jl_uv_associate_julia_struct,Void,(Ptr{Void},Any),handle,jlobj) +disassociate_julia_struct(handle::Ptr{Void}) = + ccall(:jl_uv_disassociate_julia_struct,Void,(Ptr{Void},),handle) + function _uv_tty2tty(handle::Ptr{Void}) tty = TTY(handle,true) tty.line_buffered = false - ccall(:jl_uv_associate_julia_struct,Void,(Ptr{Void},Any),handle,tty) + associate_julia_struct(handle,tty) tty end @@ -227,6 +370,7 @@ type SingleAsyncWork <: AsyncWork end this=new(cb) this.handle=ccall(:jl_make_async,Ptr{Void},(Ptr{Void},Any),loop,this) + finalizer(this,close) this end end @@ -238,6 +382,7 @@ type IdleAsyncWork <: AsyncWork function IdleAsyncWork(loop::Ptr{Void},cb::Function) this=new(cb) this.handle=ccall(:jl_make_idle,Ptr{Void},(Ptr{Void},Any),loop,this) + finalizer(this,close) this end end @@ -249,11 +394,80 @@ type TimeoutAsyncWork <: AsyncWork function TimeoutAsyncWork(loop::Ptr{Void},cb::Function) this=new(cb) this.handle=ccall(:jl_make_timer,Ptr{Void},(Ptr{Void},Any),loop,this) + finalizer(this,close) this end end TimeoutAsyncWork(cb::Function) = TimeoutAsyncWork(eventloop(),cb) +close(t::TimeoutAsyncWork) = ccall(:jl_close_uv,Void,(Ptr{Void},),t.handle) + +function poll_fd(s, events::Integer, timeout_ms::Integer) + timeout_at = int64((time() * 1000)) + timeout_ms + wt = WaitTask() + + fdw = FDWatcher(s) + start_watching((status, events) -> tasknotify([wt], :poll, status, events), fdw, events) + + if (timeout_ms > 0) + timer = TimeoutAsyncWork(status -> tasknotify([wt], :timeout, status)) + start_timer(timer, int64(timeout_ms), int64(0)) + end + + args = yield(wt) + + if (timeout_ms > 0) stop_timer(timer) end + + stop_watching(fdw) + if isa(args,InterruptException) + rethrow(args) + end + + if (args[2] != 0) error ("fd in error") end + if (args[1] == :poll) return args[3] end + if (args[1] == :timeout) return 0 end + + error("Error while polling") +end + +function poll_file(s, interval::Integer, timeout_ms::Integer) + timeout_at = int64((time() * 1000)) + timeout_ms + wt = WaitTask() + + pfw = PollingFileWatcher(s) + start_watching((status,prev,cur) -> tasknotify([wt], :poll, status), pfw, interval) + + if (timeout_ms > 0) + timer = TimeoutAsyncWork(status -> tasknotify([wt], :timeout, status)) + start_timer(timer, int64(timeout_ms), int64(0)) + end + + args = yield(wt) + + if (timeout_ms > 0) stop_timer(timer) end + + stop_watching(pfw) + if isa(args,InterruptException) + rethrow(args) + end + + if (args[2] != 0) error ("fd in error") end + if (args[1] == :poll) return 1 end + if (args[1] == :timeout) return 0 end + + error("Error while polling") +end + +function watch_file(cb, s; poll=false) + if poll + pfw = PollingFileWatcher(cb,s) + start_watching(pfw) + return pfw + else + return FileMonitor(cb,s) + end +end + function _uv_hook_close(uv::AsyncStream) uv.handle = 0 uv.open = false @@ -265,12 +479,16 @@ _uv_hook_close(uv::AsyncWork) = (uv.handle = 0; nothing) # This serves as a common callback for all async classes _uv_hook_asynccb(async::AsyncWork, status::Int32) = async.cb(status) + function start_timer(timer::TimeoutAsyncWork,timeout::Int64,repeat::Int64) + associate_julia_struct(timer.handle,timer) + ccall(:uv_update_time,Void,(Ptr{Void},),eventloop()) ccall(:jl_timer_start,Int32,(Ptr{Void},Int64,Int64),timer.handle,timeout,repeat) end function stop_timer(timer::TimeoutAsyncWork) - ccall(:jl_timer_stop,Int32,(Ptr{Void},),timer.handle) + disassociate_julia_struct(timer.handle) + ccall(:uv_timer_stop,Int32,(Ptr{Void},),timer.handle) end function sleep(sec::Real) diff --git a/base/sysimg.jl b/base/sysimg.jl index b73ab529bdd28..5c904b2f79488 100644 --- a/base/sysimg.jl +++ b/base/sysimg.jl @@ -79,6 +79,9 @@ include("set.jl") import Core.Undef # used internally by compiler include("inference.jl") +# For OS sprcific stuff in I/O +include("osutils.jl") + # I/O, strings & printing include("io.jl") include("iobuffer.jl") @@ -106,7 +109,6 @@ include("serialize.jl") include("multi.jl") # system & environment -include("osutils.jl") include("libc.jl") include("env.jl") include("errno.jl") diff --git a/src/init.c b/src/init.c index ad06332ecf7a7..f08103b65ff2a 100644 --- a/src/init.c +++ b/src/init.c @@ -312,16 +312,36 @@ DLLEXPORT void uv_atexit_hook() jl_close_uv(handle); } break; + //Don't close these directly, but rather let the GC take care of it case UV_POLL: + uv_poll_stop((uv_poll_t*)handle); + handle->data = NULL; + uv_unref(handle); + break; case UV_TIMER: - case UV_PREPARE: - case UV_CHECK: + uv_timer_stop((uv_timer_t*)handle); + handle->data = NULL; + uv_unref(handle); + break; case UV_IDLE: + uv_idle_stop((uv_idle_t*)handle); case UV_ASYNC: - case UV_SIGNAL: - case UV_PROCESS: + handle->data = NULL; + uv_unref(handle); + break; case UV_FS_EVENT: + handle->data = NULL; + uv_unref(handle); + break; case UV_FS_POLL: + uv_fs_poll_stop((uv_fs_poll_t*)handle); + handle->data = NULL; + uv_unref(handle); + break; + case UV_PREPARE: + case UV_CHECK: + case UV_SIGNAL: + case UV_PROCESS: jl_close_uv(handle); break; case UV_HANDLE: diff --git a/src/jl_uv.c b/src/jl_uv.c index e6758563ef7bf..03815c19c8e29 100644 --- a/src/jl_uv.c +++ b/src/jl_uv.c @@ -25,8 +25,18 @@ extern "C" { #endif +// To be removed once we upgrade libuv +#define uv_stat_t uv_statbuf_t /** libuv callbacks */ + +/* + * Notes for adding new callbacks + * - Make sure to type annotate the callback, so we'll get the one in the new + * Base module rather than the old one. + * + */ + //These callbacks are implemented in stream.jl #define JL_CB_TYPES(XX) \ XX(close) \ @@ -36,7 +46,10 @@ extern "C" { XX(connectcb) \ XX(connectioncb) \ XX(asynccb) \ - XX(getaddrinfo) + XX(getaddrinfo) \ + XX(pollcb) \ + XX(fspollcb) \ + XX(fseventscb) //TODO add UDP and other missing callbacks #define JULIA_HOOK_(m,hook) ((jl_function_t*)jl_get_global(m, jl_symbol("_uv_hook_" #hook))) @@ -112,8 +125,9 @@ jl_value_t *jl_callback_call(jl_function_t *f,jl_value_t *val,int count,...) void closeHandle(uv_handle_t* handle) { - JULIA_CB(close,handle->data,0); (void)ret; - //TODO: maybe notify Julia handle to close itself + if(handle->data) { + JULIA_CB(close,handle->data,0); (void)ret; + } free(handle); } @@ -165,6 +179,25 @@ void jl_asynccb(uv_handle_t *handle, int status) (void)ret; } +void jl_pollcb(uv_poll_t *handle, int status, int events) +{ + JULIA_CB(pollcb,handle->data,2,CB_INT32,status,CB_INT32,events) + (void)ret; +} + +void jl_fspollcb(uv_fs_poll_t* handle, int status, const uv_stat_t* prev, const uv_stat_t* curr) +{ + JULIA_CB(fspollcb,handle->data,3,CB_INT32,status,CB_PTR,prev,CB_PTR,curr) + (void)ret; +} + + +void jl_fseventscb(uv_fs_event_t* handle, const char* filename, int events, int status) +{ + JULIA_CB(fseventscb,handle->data,3,CB_PTR,filename,CB_INT32,events,CB_INT32,status) + (void)ret; +} + /** libuv constructors */ DLLEXPORT uv_async_t *jl_make_async(uv_loop_t *loop,jl_value_t *julia_struct) { @@ -265,6 +298,11 @@ DLLEXPORT void jl_uv_associate_julia_struct(uv_handle_t *handle, jl_value_t *dat handle->data = data; } +DLLEXPORT void jl_uv_disassociate_julia_struct(uv_handle_t *handle) +{ + handle->data = NULL; +} + DLLEXPORT int32_t jl_start_reading(uv_stream_t *handle) { if (!handle) @@ -317,7 +355,6 @@ DLLEXPORT int jl_spawn(char *name, char **argv, uv_loop_t *loop, //opts.detached = 0; #This has been removed upstream to be uncommented once it is possible again opts.exit_cb = &jl_return_spawn; error = uv_spawn(loop,proc,opts); - proc->data = julia_struct; return error; } @@ -354,15 +391,26 @@ DLLEXPORT int jl_idle_start(uv_idle_t *idle) return uv_idle_start(idle,(uv_idle_cb)&jl_asynccb); } -//units are in ms -DLLEXPORT int jl_timer_start(uv_timer_t* timer, int64_t timeout, int64_t repeat) +DLLEXPORT int jl_poll_start(uv_poll_t* handle, int32_t events) { - return uv_timer_start(timer,(uv_timer_cb)&jl_asynccb,timeout,repeat); + return uv_poll_start(handle, events, &jl_pollcb); +} + +DLLEXPORT int jl_fs_poll_start(uv_fs_poll_t* handle, char *file, uint32_t interval) +{ + return uv_fs_poll_start(handle,&jl_fspollcb,file,interval); } -DLLEXPORT int jl_timer_stop(uv_timer_t* timer) +DLLEXPORT int jl_fs_event_init(uv_loop_t* loop, uv_fs_event_t* handle, + const char* filename, int flags) +{ + return uv_fs_event_init(loop,handle,filename,&jl_fseventscb,flags); +} + +//units are in ms +DLLEXPORT int jl_timer_start(uv_timer_t* timer, int64_t timeout, int64_t repeat) { - return uv_timer_stop(timer); + return uv_timer_start(timer,(uv_timer_cb)&jl_asynccb,timeout,repeat); } DLLEXPORT int jl_puts(char *str, uv_stream_t *stream) @@ -504,6 +552,11 @@ DLLEXPORT size_t jl_sizeof_uv_stream_t() return sizeof(uv_stream_t); } +DLLEXPORT size_t jl_sizeof_uv_poll_t() +{ + return sizeof(uv_poll_t); +} + DLLEXPORT size_t jl_sizeof_uv_pipe_t() { return sizeof(uv_pipe_t); @@ -514,6 +567,16 @@ DLLEXPORT size_t jl_sizeof_uv_process_t() return sizeof(uv_process_t); } +DLLEXPORT size_t jl_sizeof_uv_fs_poll_t() +{ + return sizeof(uv_fs_poll_t); +} + +DLLEXPORT size_t jl_sizeof_uv_fs_events_t() +{ + return sizeof(jl_sizeof_uv_fs_events_t); +} + DLLEXPORT void uv_atexit_hook(); DLLEXPORT void jl_exit(int exitcode) { diff --git a/src/julia.expmap b/src/julia.expmap index fc24b6a91abc3..46089b4c4cb85 100644 --- a/src/julia.expmap +++ b/src/julia.expmap @@ -297,6 +297,7 @@ jl_tcp_bind; jl_tcp_init; jl_test; + jl_poll_start; jl_timer_start; jl_timer_stop; jl_toplevel_eval; diff --git a/test/Makefile b/test/Makefile index 82aa4f8c26f10..cbb68350de1be 100644 --- a/test/Makefile +++ b/test/Makefile @@ -4,7 +4,7 @@ include ../Make.inc TESTS = all core keywordargs numbers strings unicode corelib hashing \ remote iostring arrayops linalg blas fft dsp sparse bitarray \ random math functional bigint sorting statistics spawn parallel \ - arpack bigfloat file git pkg pkg2 suitesparse complex version + arpack bigfloat file git pkg pkg2 suitesparse complex version pollfd $(TESTS) :: $(QUIET_JULIA) $(JULIA_EXECUTABLE) ./runtests.jl $@ diff --git a/test/file.jl b/test/file.jl index 0f6c94cb94271..df4025b49afcf 100644 --- a/test/file.jl +++ b/test/file.jl @@ -42,6 +42,66 @@ mv(file, newfile) @test isfile(newfile) == true file = newfile +####################################################################### +# This section tests file watchers. # +####################################################################### +function test_file_poll(channel,timeout_ms) + rc = poll_file(file, iround(timeout_ms/10), timeout_ms) + put(channel,rc) +end + +function test_timeout(tval) + t1 = int64(time() * 1000) + channel = RemoteRef() + @async test_file_poll(channel,tval) + tr = take(channel) + t2 = int64(time() * 1000) + + @test tr == 0 + + tdiff = t2-t1 + @test tval <= tdiff +end + +function test_touch(slval) + tval = slval+100 + channel = RemoteRef() + @async test_file_poll(channel,iround(tval)) + + sleep(slval/10_000) # ~one poll period + f = open(file,"a") + write(f,"Hello World\n") + close(f) + + tr = take(channel) + + @test tr == 1 +end + + +function test_monitor(slval) + FsMonitorPassed = false + fm = FileMonitor(file) do args... + FsMonitorPassed = true + end + sleep(slval/10_000) + f = open(file,"a") + write(f,"Hello World\n") + close(f) + sleep(9slval/10_000) + @test FsMonitorPassed + close(fm) +end + +test_timeout(100) +test_timeout(1000) +test_touch(100) +test_touch(1000) +test_monitor(1000) +test_monitor(100) + + + ####################################################################### # This section tests temporary file and directory creation. # ####################################################################### diff --git a/test/pollfd.jl b/test/pollfd.jl new file mode 100644 index 0000000000000..158e01fdc349d --- /dev/null +++ b/test/pollfd.jl @@ -0,0 +1,58 @@ +@unix_only begin +require("testdefs.jl") + +t0 = int64(time() * 1000) + + +pipe_fds = Array(Cint,2) +@test 0 == ccall(:pipe, Cint, (Ptr{Cint},), pipe_fds) + +function test_poll(timeout_ms) + rc = poll_fd(OS_FD(pipe_fds[1]), UV_READABLE, timeout_ms) + produce(rc) +end + +function test_timeout(tval) + t1 = int64(time() * 1000) + t = Task(()->test_poll(tval)) + tr = consume(t) + t2 = int64(time() * 1000) + + @test tr == 0 + + tdiff = t2-t1 + @test tval <= tdiff +end + +function test_read(slval) + tval = slval + 100 + t1 = int64(time() * 1000) + t = Task(()->test_poll(tval)) + + sleep(slval/1000.0) + @test 1 == ccall(:write, Csize_t, (Cint, Ptr{Uint8},Csize_t), pipe_fds[2], bytestring("A"), 1) + + tr = consume(t) + t2 = int64(time() * 1000) + + @test tr == UV_READABLE || (UV_READABLE | UV_WRITEABLE) + + dout = Array(Uint8, 1) + @test 1 == ccall(:read, Csize_t, (Cint, Ptr{Uint8},Csize_t), pipe_fds[1], dout, 1) + @test dout[1] == int8('A') + + tdiff = t2-t1 + + @test slval <= tdiff +end + + +test_timeout(100) +test_timeout(1000) +test_read(100) +test_read(1000) + +ccall(:close, Cint, (Cint,), pipe_fds[1]) +ccall(:close, Cint, (Cint,), pipe_fds[2]) + +end diff --git a/test/runtests.jl b/test/runtests.jl index f276fec6f8b9e..3e9f27f688eba 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -3,7 +3,8 @@ testnames = ["core", "keywordargs", "numbers", "strings", "unicode", "linalg", "blas", "fft", "dsp", "sparse", "bitarray", "random", "math", "functional", "bigint", "sorting", "statistics", "spawn", "parallel", "priorityqueue", - "arpack", "bigfloat", "file", "perf", "suitesparse", "version"] + "arpack", "bigfloat", "file", "perf", "suitesparse", "version", + "pollfd"] # Disabled: "complex"