Skip to content

Commit

Permalink
Merge pull request #11 from vtjnash/jn/nextrelease
Browse files Browse the repository at this point in the history
make the v1.3 release, with more features
  • Loading branch information
vtjnash authored Feb 18, 2022
2 parents fa5b193 + 4466af1 commit 6d58b72
Show file tree
Hide file tree
Showing 5 changed files with 252 additions and 89 deletions.
14 changes: 12 additions & 2 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,23 @@ jobs:
matrix:
version:
- '1.0'
- '1'
- '1.6'
- '1.7'
- 'nightly'
os:
- ubuntu-latest
arch:
- x86
- x64
include:
- version: '1'
os: ubuntu-latest
arch: x86
- version: '1'
os: windows-latest
arch: x64
- version: '1'
os: macos-latest
arch: x64
steps:
- uses: actions/checkout@v2
- uses: julia-actions/setup-julia@v1
Expand Down
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "Pidfile"
uuid = "fa939f87-e72e-5be4-a000-7fc836dbe307"
version = "1.2.0"
version = "1.3.0"

[deps]
FileWatching = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee"
Expand Down
1 change: 1 addition & 0 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ Pidfile.write_pidfile
Pidfile.parse_pidfile
Pidfile.stale_pidfile
Pidfile.isvalidpid
Base.touch(::Pidfile.LockMonitor)
```
153 changes: 122 additions & 31 deletions src/Pidfile.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ using Base:
IOError, UV_EEXIST, UV_ESRCH,
Process

using Base.Libc: rand

using Base.Filesystem:
File, open, JL_O_CREAT, JL_O_RDWR, JL_O_RDONLY, JL_O_EXCL,
samefile
rename, samefile, path_separator

using FileWatching: watch_file
using Base.Sys: iswindows
Expand All @@ -23,32 +25,50 @@ or the process identified by pid or proc. Can take a function to execute once lo
for usage in `do` blocks, after which the lock will be automatically closed. If the lock fails
and `wait` is false, then an error is thrown.
The lock will be released by either `close`, a `finalizer`, or shortly after `proc` exits.
Make sure the return value is live through the end of the critical section of
your program, so the `finalizer` does not reclaim it early.
Optional keyword arguments:
- `mode`: file access mode (modified by the process umask). Defaults to world-readable.
- `poll_interval`: Specify the maximum time to between attempts (if `watch_file` doesn't work)
- `stale_age`: Delete an existing pidfile (ignoring the lock) if its mtime is older than this.
The file won't be deleted until 25x longer than this if the pid in the file appears that it may be valid.
By default this is disabled (`stale_age` = 0), but a typical recommended value would be about 3-5x an
estimated normal completion time.
- `refresh`: Keeps a lock from becoming stale by updating the mtime every interval of time that passes.
By default, this is set to `stale_age/2`, which is the recommended value.
- `wait`: If true, block until we get the lock, if false, raise error if lock fails.
"""
function mkpidlock end

macro constfield(ex) esc(VERSION >= v"1.8-" ? Expr(:const, ex) : ex) end

# mutable only because we want to add a finalizer
mutable struct LockMonitor
path::String
fd::File
@constfield path::String
@constfield fd::File
@constfield update::Union{Nothing,Timer}

global function mkpidlock(at::String, pid::Cint; kwopts...)
global function mkpidlock(at::String, pid::Cint; stale_age::Real=0, refresh::Real=stale_age/2, kwopts...)
local lock
at = abspath(at)
fd = open_exclusive(at; kwopts...)
atdir, atname = splitdir(at)
isempty(atdir) && (atdir = pwd())
at = realpath(atdir) * path_separator * atname
fd = open_exclusive(at; stale_age=stale_age, kwopts...)
update = nothing
try
write_pidfile(fd, pid)
lock = new(at, fd)
if refresh > 0
# N.b.: to ensure our finalizer works we are careful to capture
# `fd` here instead of `lock`.
update = Timer(t -> isopen(t) && touch(fd), refresh; interval=refresh)
end
lock = new(at, fd, update)
finalizer(close, lock)
catch ex
close(fd)
rm(at)
close(fd)
rethrow(ex)
end
return lock
Expand All @@ -67,17 +87,38 @@ function mkpidlock(f::Function, at::String, pid::Cint; kwopts...)
end
end

# TODO: enable this when we update libuv
#Base.getpid(proc::Process) = ccall(:uv_process_get_pid, Cint, (Ptr{Void},), proc.handle)
#function mkpidlock(at::String, proc::Process; kwopts...)
# lock = mkpidlock(at, getpid(proc))
# @schedule begin
# wait(proc)
# close(lock)
# end
# return lock
#end
if VERSION >= v"1.1" # getpid(::Proc) added
function mkpidlock(at::String, proc::Process; kwopts...)
lock = mkpidlock(at, getpid(proc); kwopts...)
closer = @async begin
wait(proc)
close(lock)
end
isdefined(Base, :errormonitor) && Base.errormonitor(closer)
return lock
end
end

"""
Base.touch(::Pidfile.LockMonitor)
Update the `mtime` on the lock, to indicate it is still fresh.
See also the `refresh` keyword in the [`mkpidlock`](@ref) constructor.
"""
Base.touch(lock::LockMonitor) = (touch(lock.fd); lock)

if hasmethod(Base, Tuple{File})
# added in Julia v1.9
const touch = Base.touch
else
touch(f) = Base.touch(f)
function touch(f::File)
now = time()
Base.Filesystem.futime(f, now, now)
f
end
end

"""
write_pidfile(io, pid)
Expand Down Expand Up @@ -188,11 +229,7 @@ function open_exclusive(path::String;
if file === nothing && stale_age > 0
if stale_age > 0 && stale_pidfile(path, stale_age)
@warn "attempting to remove probably stale pidfile" path=path
try
rm(path)
catch ex
isa(ex, IOError) || rethrow(ex)
end
tryrmopenfile(path)
end
file = tryopen_exclusive(path, mode)
end
Expand Down Expand Up @@ -221,13 +258,46 @@ function open_exclusive(path::String;
# set stale_age to zero so we won't attempt again, even if the attempt fails
stale_age -= stale_age
@warn "attempting to remove probably stale pidfile" path=path
try
rm(path)
catch ex
isa(ex, IOError) || rethrow(ex)
tryrmopenfile(path)
end
end
end

function _rand_filename(len::Int=4) # modified from Base.Libc
slug = Base.StringVector(len)
chars = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
for i = 1:len
slug[i] = chars[(Libc.rand() % length(chars)) + 1]
end
return String(slug)
end

function tryrmopenfile(path::String)
# Deleting open file on Windows is a bit hard
# if we want to reuse the name immediately after:
# we need to first rename it, then delete it.
if Sys.iswindows()
try
local rmpath
rmdir, rmname = splitdir(path)
while true
rmpath = string(rmdir, isempty(rmdir) ? "" : path_separator,
"\$", _rand_filename(), rmname, ".deleted")
ispath(rmpath) || break
end
rename(path, rmpath)
path = rmpath
catch ex
isa(ex, IOError) || rethrow(ex)
end
end
return try
rm(path)
true
catch ex
isa(ex, IOError) || rethrow(ex)
false
end
end

"""
Expand All @@ -236,12 +306,33 @@ end
Release a pidfile lock.
"""
function Base.close(lock::LockMonitor)
update = lock.update
update === nothing || close(update)
isopen(lock.fd) || return false
havelock = samefile(stat(lock.fd), stat(lock.path))
close(lock.fd)
if havelock # try not to delete someone else's lock
rm(lock.path)
removed = false
path = lock.path
pathstat = try
# Windows sometimes likes to return EACCES here,
# if the path is in the process of being deleted
stat(path)
catch ex
ex isa IOError || rethrow()
removed = ex
nothing
end
if pathstat !== nothing && samefile(stat(lock.fd), pathstat)
# try not to delete someone else's lock
try
rm(path)
removed = true
catch ex
ex isa IOError || rethrow()
removed = ex
end
end
close(lock.fd)
havelock = removed === true
havelock || @warn "failed to remove pidfile on close" path=path removed=removed
return havelock
end

Expand Down
Loading

2 comments on commit 6d58b72

@vtjnash
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/54930

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v1.3.0 -m "<description of version>" 6d58b726ed94dc2e3512806587ca6a11fb9b21f0
git push origin v1.3.0

Please sign in to comment.