diff --git a/base/deprecated.jl b/base/deprecated.jl index 9927285ba7d26..c819988547a24 100644 --- a/base/deprecated.jl +++ b/base/deprecated.jl @@ -473,8 +473,6 @@ end @deprecate den denominator @deprecate num numerator -Filesystem.stop_watching(stream::Filesystem._FDWatcher) = depwarn("stop_watching(::_FDWatcher) should not be used", :stop_watching) - # #19088 @deprecate takebuf_array take! @deprecate takebuf_string(b) String(take!(b)) @@ -1361,6 +1359,12 @@ export conv, conv2, deconv, filt, filt!, xcorr @deprecate_moved Base64EncodePipe "Base64" true true @deprecate_moved Base64DecodePipe "Base64" true true +@deprecate_moved poll_fd "FileWatching" true true +@deprecate_moved poll_file "FileWatching" true true +@deprecate_moved PollingFileWatcher "FileWatching" true true +@deprecate_moved watch_file "FileWatching" true true +@deprecate_moved FileMonitor "FileWatching" true true + # PR #21709 @deprecate cov(x::AbstractVector, corrected::Bool) cov(x, corrected=corrected) @deprecate cov(x::AbstractMatrix, vardim::Int, corrected::Bool) cov(x, vardim, corrected=corrected) diff --git a/base/event.jl b/base/event.jl index fa6e74898ced1..f3b67726fea2e 100644 --- a/base/event.jl +++ b/base/event.jl @@ -32,8 +32,7 @@ Block the current task until some event occurs, depending on the type of the arg can be used to determine success or failure. * [`Task`](@ref): Wait for a `Task` to finish, returning its result value. If the task fails with an exception, the exception is propagated (re-thrown in the task that called `wait`). -* `RawFD`: Wait for changes on a file descriptor (see [`poll_fd`](@ref) for keyword - arguments and return code) +* `RawFD`: Wait for changes on a file descriptor (see the `FileWatching` package). If no argument is passed, the task blocks for an undefined period. A task can only be restarted by an explicit call to [`schedule`](@ref) or [`yieldto`](@ref). diff --git a/base/exports.jl b/base/exports.jl index 1bf92e86fb631..0e30396010261 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -62,7 +62,6 @@ export Enumerate, ExponentialBackOff, Factorization, - FileMonitor, Hermitian, UniformScaling, IndexCartesian, @@ -84,7 +83,6 @@ export Pair, PartialQuickSort, PermutedDimsArray, - PollingFileWatcher, QuickSort, RangeIndex, Rational, @@ -1027,8 +1025,6 @@ export pipeline, Pipe, PipeBuffer, - poll_fd, - poll_file, position, RawFD, read, @@ -1056,7 +1052,6 @@ export take!, truncate, unmark, - watch_file, write, TCPSocket, UDPSocket, diff --git a/base/filesystem.jl b/base/filesystem.jl index 48e136d86f924..c5f8e4b10854d 100644 --- a/base/filesystem.jl +++ b/base/filesystem.jl @@ -51,7 +51,6 @@ end include("path.jl") include("stat.jl") include("file.jl") -include("poll.jl") include(string(length(Core.ARGS) >= 2 ? Core.ARGS[2] : "", "file_constants.jl")) # include($BUILDROOT/base/file_constants.jl) ## Operations with File (fd) objects ## diff --git a/base/io.jl b/base/io.jl index f64273602ebba..bd7b884e3f4ac 100644 --- a/base/io.jl +++ b/base/io.jl @@ -36,7 +36,7 @@ buffer_writes(x::IO, bufsize=SZ_UNBUFFERED_IO) = x Determine whether an object - such as a stream, timer, or mmap -- is not yet closed. Once an object is closed, it will never produce a new event. However, a closed stream may still have data to read in its buffer, use [`eof`](@ref) to check for the ability to read data. -Use [`poll_fd`](@ref) to be notified when a stream might be writable or readable. +Use the `FileWatching` package to be notified when a stream might be writable or readable. """ function isopen end diff --git a/doc/make.jl b/doc/make.jl index f4b4512770108..7bff07d8962e7 100644 --- a/doc/make.jl +++ b/doc/make.jl @@ -27,6 +27,7 @@ if Sys.iswindows() cp_q("../stdlib/SharedArrays/docs/src/index.md", "src/stdlib/sharedarrays.md") cp_q("../stdlib/Profile/docs/src/index.md", "src/stdlib/profile.md") cp_q("../stdlib/Base64/docs/src/index.md", "src/stdlib/base64.md") + cp_q("../stdlib/FileWatching/docs/src/index.md", "src/stdlib/filewatching.md") else symlink_q("../../../stdlib/DelimitedFiles/docs/src/index.md", "src/stdlib/delimitedfiles.md") symlink_q("../../../stdlib/Test/docs/src/index.md", "src/stdlib/test.md") @@ -34,6 +35,7 @@ else symlink_q("../../../stdlib/SharedArrays/docs/src/index.md", "src/stdlib/sharedarrays.md") symlink_q("../../../stdlib/Profile/docs/src/index.md", "src/stdlib/profile.md") symlink_q("../../../stdlib/Base64/docs/src/index.md", "src/stdlib/base64.md") + symlink_q("../../../stdlib/FileWatching/docs/src/index.md", "src/stdlib/filewatching.md") end const PAGES = [ @@ -104,6 +106,9 @@ const PAGES = [ "stdlib/stacktraces.md", "stdlib/simd-types.md", "stdlib/base64.md", + "stdlib/mmap.md", + "stdlib/sharedarrays.md", + "stdlib/filewatching.md", ], "Developer Documentation" => [ "devdocs/reflection.md", @@ -138,11 +143,11 @@ const PAGES = [ ], ] -using DelimitedFiles, Test, Mmap, SharedArrays, Profile, Base64 +using DelimitedFiles, Test, Mmap, SharedArrays, Profile, Base64, FileWatching makedocs( build = joinpath(pwd(), "_build/html/en"), - modules = [Base, Core, BuildSysImg, DelimitedFiles, Test, Mmap, SharedArrays, Profile, Base64], + modules = [Base, Core, BuildSysImg, DelimitedFiles, Test, Mmap, SharedArrays, Profile, Base64, FileWatching], clean = false, doctest = "doctest" in ARGS, linkcheck = "linkcheck" in ARGS, diff --git a/doc/src/index.md b/doc/src/index.md index d1028f46261ad..2c7fa3ae8c0dd 100644 --- a/doc/src/index.md +++ b/doc/src/index.md @@ -53,6 +53,7 @@ * [Linear Algebra](@ref) * [Constants](@ref lib-constants) * [Filesystem](@ref) + * [Delimited Files](@ref) * [I/O and Network](@ref) * [Punctuation](@ref) * [Sorting and Related Functions](@ref) @@ -63,9 +64,13 @@ * [C Interface](@ref) * [C Standard Library](@ref) * [Dynamic Linker](@ref) - * [Profiling](@ref lib-profiling) * [StackTraces](@ref) * [SIMD Support](@ref) + * [Profiling](@ref lib-profiling) + * [Memory-mapped I/O](@ref) + * [Shared Arrays](@ref) + * [Base64](@ref) + * [File Events](@ref lib-filewatching) ## Developer Documentation diff --git a/doc/src/stdlib/index.md b/doc/src/stdlib/index.md index 4fcb10d71e77d..be772c5f695e9 100644 --- a/doc/src/stdlib/index.md +++ b/doc/src/stdlib/index.md @@ -22,7 +22,10 @@ * [LLVM Interface](@ref) * [C Standard Library](@ref) * [Dynamic Linker](@ref) - * [Profiling](@ref lib-profiling) + * [StackTraces](@ref) * [SIMD Support](@ref) + * [Profiling](@ref lib-profiling) * [Memory-mapped I/O](@ref) * [Shared Arrays](@ref) + * [Base64](@ref) + * [File Events](@ref lib-filewatching) diff --git a/doc/src/stdlib/io-network.md b/doc/src/stdlib/io-network.md index 7401410a8dbc3..a25fca47fda1b 100644 --- a/doc/src/stdlib/io-network.md +++ b/doc/src/stdlib/io-network.md @@ -149,9 +149,6 @@ Base.IPv6 Base.nb_available Base.accept Base.listenany -Base.Filesystem.poll_fd -Base.Filesystem.poll_file -Base.Filesystem.watch_file Base.bind Base.send Base.recv diff --git a/stdlib/FileWatching/docs/src/index.md b/stdlib/FileWatching/docs/src/index.md new file mode 100644 index 0000000000000..7c42ddca9472b --- /dev/null +++ b/stdlib/FileWatching/docs/src/index.md @@ -0,0 +1,7 @@ +# [File Events](@id lib-filewatching) + +```@docs +FileWatching.poll_fd +FileWatching.poll_file +FileWatching.watch_file +``` diff --git a/base/poll.jl b/stdlib/FileWatching/src/FileWatching.jl similarity index 98% rename from base/poll.jl rename to stdlib/FileWatching/src/FileWatching.jl index 7f1ef55034cd7..5d2735dac8244 100644 --- a/base/poll.jl +++ b/stdlib/FileWatching/src/FileWatching.jl @@ -1,6 +1,9 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -# filesystem operations +""" +Utilities for monitoring files and file descriptors for events. +""" +module FileWatching export watch_file, @@ -11,8 +14,9 @@ export FDWatcher import Base: @handle_as, wait, close, uvfinalize, eventloop, notify_error, stream_wait, - _sizeof_uv_poll, _sizeof_uv_fs_poll, _sizeof_uv_fs_event, _uv_hook_close, + _sizeof_uv_poll, _sizeof_uv_fs_poll, _sizeof_uv_fs_event, _uv_hook_close, uv_error, UVError, associate_julia_struct, disassociate_julia_struct, isreadable, iswritable, | +import Base.Filesystem.StatStruct if Sys.iswindows() import Base.WindowsRawSocket end @@ -563,3 +567,9 @@ function poll_file(s::AbstractString, interval_seconds::Real=5.007, timeout_s::R close(pfw) end end + +# deprecations + +stop_watching(stream::_FDWatcher) = Base.depwarn("stop_watching(::_FDWatcher) should not be used", :stop_watching) + +end diff --git a/test/pollfd.jl b/stdlib/FileWatching/test/runtests.jl similarity index 57% rename from test/pollfd.jl rename to stdlib/FileWatching/test/runtests.jl index 4a9eedd353efe..d6b0c3aeb7e0d 100644 --- a/test/pollfd.jl +++ b/stdlib/FileWatching/test/runtests.jl @@ -1,5 +1,7 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +using Test, FileWatching + # This script does the following # Sets up n unix pipes # For the odd pipes, a byte is written to the write end at intervals specified in intvls @@ -145,3 +147,142 @@ let a = Ref(0) gc() @test a[] == 1 end + +for f in (watch_file, poll_file) + local f + @test_throws ArgumentError f("adir\0bad") +end + +#issue #12992 +function test_12992() + pfw = PollingFileWatcher(@__FILE__, 0.01) + close(pfw) + pfw = PollingFileWatcher(@__FILE__, 0.01) + close(pfw) + pfw = PollingFileWatcher(@__FILE__, 0.01) + close(pfw) + gc() + gc() +end + +# Make sure multiple close is fine +function test2_12992() + pfw = PollingFileWatcher(@__FILE__, 0.01) + close(pfw) + close(pfw) + pfw = PollingFileWatcher(@__FILE__, 0.01) + close(pfw) + close(pfw) + pfw = PollingFileWatcher(@__FILE__, 0.01) + close(pfw) + close(pfw) + gc() + gc() +end + +test_12992() +test_12992() +test_12992() + +test2_12992() +test2_12992() +test2_12992() + +####################################################################### +# This section tests file watchers. # +####################################################################### +dir = mktempdir() +file = joinpath(dir, "afile.txt") +# like touch, but lets the operating system update the timestamp +# for greater precision on some platforms (windows) +@test close(open(file,"w")) === nothing + +function test_file_poll(channel,interval,timeout_s) + rc = poll_file(file, interval, timeout_s) + put!(channel,rc) +end + +function test_timeout(tval) + t_elapsed = @elapsed begin + channel = Channel(1) + @async test_file_poll(channel, 10, tval) + tr = take!(channel) + end + @test tr[1] === Base.Filesystem.StatStruct() && tr[2] === EOFError() + @test tval <= t_elapsed +end + +function test_touch(slval) + tval = slval*1.1 + channel = Channel(1) + @async test_file_poll(channel, tval/3, tval) + sleep(tval/3) # one poll period + f = open(file,"a") + write(f,"Hello World\n") + close(f) + tr = take!(channel) + @test ispath(tr[1]) && ispath(tr[2]) +end + +function test_watch_file_timeout(tval) + watch = @async watch_file(file, tval) + @test wait(watch) == FileWatching.FileEvent(false, false, true) +end + +function test_watch_file_change(tval) + watch = @async watch_file(file, tval) + sleep(tval/3) + open(file, "a") do f + write(f, "small change\n") + end + @test wait(watch) == FileWatching.FileEvent(false, true, false) +end + +function test_monitor_wait(tval) + fm = FileMonitor(file) + @async begin + sleep(tval) + f = open(file,"a") + write(f,"Hello World\n") + close(f) + end + fname, events = wait(fm) + close(fm) + if Sys.islinux() || Sys.iswindows() || Sys.isapple() + @test fname == basename(file) + else + @test fname == "" # platforms where F_GETPATH is not available + end + @test events.changed +end + +function test_monitor_wait_poll() + pfw = PollingFileWatcher(file, 5.007) + @async begin + sleep(2.5) + f = open(file,"a") + write(f,"Hello World\n") + close(f) + end + (old, new) = wait(pfw) + close(pfw) + @test new.mtime - old.mtime > 2.5 - 1.5 # mtime may only have second-level accuracy (plus add some hysteresis) +end + +test_timeout(0.1) +test_timeout(1) +test_touch(6) +test_monitor_wait(0.1) +test_monitor_wait(0.1) +test_monitor_wait_poll() +test_monitor_wait_poll() +test_watch_file_timeout(0.1) +test_watch_file_change(6) + +@test_throws Base.UVError watch_file("____nonexistent_file", 10) +@test(@elapsed( + @test(poll_file("____nonexistent_file", 1, 3.1) === + (Base.Filesystem.StatStruct(), EOFError()))) > 3) + +rm(file) +rm(dir) diff --git a/test/choosetests.jl b/test/choosetests.jl index d3aa1a7b8aa4c..9a749d5bc4e6a 100644 --- a/test/choosetests.jl +++ b/test/choosetests.jl @@ -38,7 +38,7 @@ function choosetests(choices = []) "operators", "path", "ccall", "parse", "loading", "bigint", "bigfloat", "sorting", "statistics", "spawn", "backtrace", "file", "read", "version", "resolve", - "pollfd", "mpfr", "broadcast", "complex", "socket", + "mpfr", "broadcast", "complex", "socket", "floatapprox", "stdlib", "reflection", "regex", "float16", "combinatorics", "sysinfo", "env", "rounding", "ranges", "mod2pi", "euler", "show", "lineedit", "replcompletions", "repl", diff --git a/test/file.jl b/test/file.jl index 587470fb6899f..d699cac1399bf 100644 --- a/test/file.jl +++ b/test/file.jl @@ -183,96 +183,6 @@ else @test chown(file, -2, -2) === nothing end -####################################################################### -# This section tests file watchers. # -####################################################################### -function test_file_poll(channel,interval,timeout_s) - rc = poll_file(file, interval, timeout_s) - put!(channel,rc) -end - -function test_timeout(tval) - t_elapsed = @elapsed begin - channel = Channel(1) - @async test_file_poll(channel, 10, tval) - tr = take!(channel) - end - @test tr[1] === Base.Filesystem.StatStruct() && tr[2] === EOFError() - @test tval <= t_elapsed -end - -function test_touch(slval) - tval = slval*1.1 - channel = Channel(1) - @async test_file_poll(channel, tval/3, tval) - sleep(tval/3) # one poll period - f = open(file,"a") - write(f,"Hello World\n") - close(f) - tr = take!(channel) - @test ispath(tr[1]) && ispath(tr[2]) -end - -function test_watch_file_timeout(tval) - watch = @async watch_file(file, tval) - @test wait(watch) == Base.Filesystem.FileEvent(false, false, true) -end - -function test_watch_file_change(tval) - watch = @async watch_file(file, tval) - sleep(tval/3) - open(file, "a") do f - write(f, "small change\n") - end - @test wait(watch) == Base.Filesystem.FileEvent(false, true, false) -end - -function test_monitor_wait(tval) - fm = FileMonitor(file) - @async begin - sleep(tval) - f = open(file,"a") - write(f,"Hello World\n") - close(f) - end - fname, events = wait(fm) - close(fm) - if Sys.islinux() || Sys.iswindows() || Sys.isapple() - @test fname == basename(file) - else - @test fname == "" # platforms where F_GETPATH is not available - end - @test events.changed -end - -function test_monitor_wait_poll() - pfw = PollingFileWatcher(file, 5.007) - @async begin - sleep(2.5) - f = open(file,"a") - write(f,"Hello World\n") - close(f) - end - (old, new) = wait(pfw) - close(pfw) - @test new.mtime - old.mtime > 2.5 - 1.5 # mtime may only have second-level accuracy (plus add some hysteresis) -end - -test_timeout(0.1) -test_timeout(1) -test_touch(6) -test_monitor_wait(0.1) -test_monitor_wait(0.1) -test_monitor_wait_poll() -test_monitor_wait_poll() -test_watch_file_timeout(0.1) -test_watch_file_change(6) - -@test_throws Base.UVError watch_file("____nonexistent_file", 10) -@test(@elapsed( - @test(poll_file("____nonexistent_file", 1, 3.1) === - (Base.Filesystem.StatStruct(), EOFError()))) > 3) - ############## # mark/reset # ############## @@ -933,7 +843,7 @@ end for f in (mkdir, cd, Base.Filesystem.unlink, readlink, rm, touch, readdir, mkpath, stat, lstat, ctime, mtime, filemode, filesize, uperm, gperm, operm, touch, isblockdev, ischardev, isdir, isfifo, isfile, islink, ispath, issetgid, - issetuid, issocket, issticky, realpath, watch_file, poll_file) + issetuid, issocket, issticky, realpath) local f @test_throws ArgumentError f("adir\0bad") end @@ -1081,41 +991,6 @@ let n = tempname() rm(n) end -#issue #12992 -function test_12992() - pfw = PollingFileWatcher(@__FILE__, 0.01) - close(pfw) - pfw = PollingFileWatcher(@__FILE__, 0.01) - close(pfw) - pfw = PollingFileWatcher(@__FILE__, 0.01) - close(pfw) - gc() - gc() -end - -# Make sure multiple close is fine -function test2_12992() - pfw = PollingFileWatcher(@__FILE__, 0.01) - close(pfw) - close(pfw) - pfw = PollingFileWatcher(@__FILE__, 0.01) - close(pfw) - close(pfw) - pfw = PollingFileWatcher(@__FILE__, 0.01) - close(pfw) - close(pfw) - gc() - gc() -end - -test_12992() -test_12992() -test_12992() - -test2_12992() -test2_12992() -test2_12992() - # issue 13559 if !Sys.iswindows() function test_13559()