Compare commits
9 Commits
Author | SHA1 | Date |
---|---|---|
Denis Knauf | f963e41c52 | |
Denis Knauf | 5b390b2083 | |
Denis Knauf | 3542837d7f | |
Denis Knauf | 28936187de | |
Denis Knauf | 486cf53495 | |
Denis Knauf | ae8f1e0a33 | |
Denis Knauf | 3ca2de0cd9 | |
Denis Knauf | 9fc25c3015 | |
Denis Knauf | f27600ce16 |
|
@ -1,49 +1,18 @@
|
|||
# rcov generated
|
||||
coverage
|
||||
coverage.data
|
||||
|
||||
# rdoc generated
|
||||
rdoc
|
||||
|
||||
# yard generated
|
||||
doc
|
||||
.yardoc
|
||||
|
||||
# bundler
|
||||
.bundle
|
||||
|
||||
# jeweler generated
|
||||
Gemfile.lock
|
||||
pkg
|
||||
|
||||
# Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore:
|
||||
#
|
||||
# * Create a file at ~/.gitignore
|
||||
# * Include files you want ignored
|
||||
# * Run: git config --global core.excludesfile ~/.gitignore
|
||||
#
|
||||
# After doing this, these files will be ignored in all your git projects,
|
||||
# saving you from having to 'pollute' every project you touch with them
|
||||
#
|
||||
# Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line)
|
||||
#
|
||||
# For MacOS:
|
||||
#
|
||||
.DS_Store
|
||||
|
||||
# For TextMate
|
||||
*.tmproj
|
||||
tmtags
|
||||
|
||||
# For emacs:
|
||||
*~
|
||||
\#*
|
||||
.\#*
|
||||
|
||||
# For vim:
|
||||
*.swp
|
||||
|
||||
# For redcar:
|
||||
*.sw[p-n]
|
||||
.redcar
|
||||
|
||||
# For rubinius:
|
||||
*.rbc
|
||||
.document
|
||||
|
|
17
Gemfile
17
Gemfile
|
@ -1,15 +1,2 @@
|
|||
source "http://rubygems.org"
|
||||
|
||||
gem 'ffi-libc'
|
||||
|
||||
# Add dependencies to develop your gem here.
|
||||
# Include everything needed to run rake, tests, features, etc.
|
||||
group :development do
|
||||
gem "shoulda"
|
||||
gem "yard"
|
||||
gem "redcarpet"
|
||||
gem "rdoc"
|
||||
gem "bundler"
|
||||
gem "jeweler"
|
||||
gem "simplecov"
|
||||
end
|
||||
source "https://rubygems.org"
|
||||
gemspec
|
||||
|
|
52
Gemfile.lock
52
Gemfile.lock
|
@ -1,52 +0,0 @@
|
|||
GEM
|
||||
remote: http://rubygems.org/
|
||||
specs:
|
||||
activesupport (3.2.12)
|
||||
i18n (~> 0.6)
|
||||
multi_json (~> 1.0)
|
||||
bourne (1.1.2)
|
||||
mocha (= 0.10.5)
|
||||
ffi (1.1.0)
|
||||
ffi-libc (0.0.5)
|
||||
ffi (>= 0.6.0, <= 1.1.0)
|
||||
git (1.2.5)
|
||||
i18n (0.6.4)
|
||||
jeweler (1.8.4)
|
||||
bundler (~> 1.0)
|
||||
git (>= 1.2.5)
|
||||
rake
|
||||
rdoc
|
||||
json (1.7.7)
|
||||
metaclass (0.0.1)
|
||||
mocha (0.10.5)
|
||||
metaclass (~> 0.0.1)
|
||||
multi_json (1.6.1)
|
||||
rake (10.0.3)
|
||||
rdoc (4.0.0)
|
||||
json (~> 1.4)
|
||||
redcarpet (2.2.2)
|
||||
shoulda (3.3.2)
|
||||
shoulda-context (~> 1.0.1)
|
||||
shoulda-matchers (~> 1.4.1)
|
||||
shoulda-context (1.0.2)
|
||||
shoulda-matchers (1.4.2)
|
||||
activesupport (>= 3.0.0)
|
||||
bourne (~> 1.1.2)
|
||||
simplecov (0.7.1)
|
||||
multi_json (~> 1.0)
|
||||
simplecov-html (~> 0.7.1)
|
||||
simplecov-html (0.7.1)
|
||||
yard (0.8.5.2)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
bundler
|
||||
ffi-libc
|
||||
jeweler
|
||||
rdoc
|
||||
redcarpet
|
||||
shoulda
|
||||
simplecov
|
||||
yard
|
28
README.md
28
README.md
|
@ -1,11 +1,12 @@
|
|||
timeout-interrupt
|
||||
=================
|
||||
|
||||
Works like ruby's timeout, but interrupts every call, also syscalls, which blocks the hole ruby-process.
|
||||
Works like ruby's timeout, but interrupts *every call*, also syscalls, which blocks the hole ruby-process.
|
||||
|
||||
It uses POSIX's alarm and traps ALRM-signals.
|
||||
|
||||
Known limitations bacause of alarm and ALRM are, that you can not use alarm or trap ALRM.
|
||||
Known limitations bacause of alarm and ALRM are, that you can not use alarm or trap ALRM in the same time.
|
||||
|
||||
|
||||
Scopes
|
||||
======
|
||||
|
@ -23,27 +24,30 @@ If you want to know, which was raised, you need custom exceptions:
|
|||
|
||||
class CustomErrorWillBeRaised <Exception
|
||||
end
|
||||
class CustomErrorNotRaise <Exception
|
||||
class CustomErrorNotRaised <Exception
|
||||
end
|
||||
include TimeoutInterrupt
|
||||
timeout( 1, CustomErrorWillBeRaised) { # Will be raised again
|
||||
timeout( 10, CustomErrorNotRaise) { sleep 2 } # Will not be raised
|
||||
timeout( 10, CustomErrorNotRaised) { sleep 2 } # Will not be raised
|
||||
}
|
||||
|
||||
|
||||
Problems
|
||||
========
|
||||
|
||||
Memory-Leaks or no clean up
|
||||
---------------------------
|
||||
Memory-Leaks and missing clean up
|
||||
---------------------------------
|
||||
|
||||
Do not forget, syscall can have allocated memory.
|
||||
If you interrupt a call, which can not free his allocations, you will have a memory leak.
|
||||
If it opens a file, reads it and closes it and while it reads, a time out occurs, the file will not be closed.
|
||||
Syscalls can allocate memory.
|
||||
If you interrupt a syscall, which then cannot free his allocations, it will result in a memory leak.
|
||||
If it opens a file, while it reads, a time out occurs, the file will not automatically be closed.
|
||||
|
||||
So, use it only, if your process did not live any longer or if you call something, which never allocate mem or opens a file.
|
||||
So, you should only use it, if your process will die after interrupt or if you are sure, that your call never allocate memory or opens a file.
|
||||
You could also publish informations about opened files, that it could be cleaned up later.
|
||||
|
||||
Every time, a process dies, all his memory will be freed and every file will be closed, so let your process die and you should be safe.
|
||||
|
||||
|
||||
Exception-handling
|
||||
------------------
|
||||
|
||||
|
@ -60,11 +64,11 @@ Timeouts can break your exception-handling! You should not handling exception wh
|
|||
end
|
||||
}
|
||||
|
||||
Same happens, if clean\_up will raise an exception.
|
||||
Same happens, if clean\_up will raise an exception, so handle it in the same way.
|
||||
|
||||
And same problem you have with ruby's `Timeout.timeout`.
|
||||
|
||||
Copyleft
|
||||
=========
|
||||
|
||||
Copyright (c) 2013 Denis Knauf. See LICENSE.txt for further details.
|
||||
Copyright (c) 2021 Denis Knauf. See LICENSE.txt for further details.
|
43
Rakefile
43
Rakefile
|
@ -1,46 +1,9 @@
|
|||
# encoding: utf-8
|
||||
|
||||
require 'rubygems'
|
||||
require 'bundler'
|
||||
begin
|
||||
Bundler.setup(:default, :development)
|
||||
rescue Bundler::BundlerError => e
|
||||
$stderr.puts e.message
|
||||
$stderr.puts "Run `bundle install` to install missing gems"
|
||||
exit e.status_code
|
||||
end
|
||||
require 'rake'
|
||||
|
||||
require 'jeweler'
|
||||
Jeweler::Tasks.new do |gem|
|
||||
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
||||
gem.name = "timeout-interrupt"
|
||||
gem.homepage = "http://github.com/DenisKnauf/ruby-timeout-interrupt"
|
||||
gem.license = "LGPLv3"
|
||||
gem.summary = %Q{"Interrupts systemcalls too."}
|
||||
gem.description = %Q{Timeout-lib, which interrupts everything, also systemcalls. It uses libc-alarm.}
|
||||
gem.email = "Denis.Knauf@gmail.com"
|
||||
gem.authors = ["Denis Knauf"]
|
||||
# dependencies defined in Gemfile
|
||||
end
|
||||
Jeweler::RubygemsDotOrgTasks.new
|
||||
require "bundler/gem_tasks"
|
||||
task :default => :spec
|
||||
|
||||
require 'rake/testtask'
|
||||
Rake::TestTask.new(:test) do |test|
|
||||
Rake::TestTask.new :test do |test|
|
||||
test.libs << 'lib' << 'test'
|
||||
test.pattern = 'test/**/test_*.rb'
|
||||
test.verbose = true
|
||||
end
|
||||
|
||||
#require 'simplecov'
|
||||
#Rcov::RcovTask.new do |test|
|
||||
#test.libs << 'test'
|
||||
#test.pattern = 'test/**/test_*.rb'
|
||||
#test.verbose = true
|
||||
#test.rcov_opts << '--exclude "gems/*"'
|
||||
#end
|
||||
|
||||
task :default => :test
|
||||
|
||||
require 'yard'
|
||||
YARD::Rake::YardocTask.new
|
||||
|
|
|
@ -1,23 +1,6 @@
|
|||
require 'ffi/libc'
|
||||
require 'timeout'
|
||||
|
||||
# Provided by ffi-libc-lib and extended by this library, if needed.
|
||||
# Older version of ffi-libc does not provide {FFI::LibC.alarm}
|
||||
module FFI
|
||||
module LibC
|
||||
# @!method alarm(seconds)
|
||||
# Sets an alarm. After `seconds` it will send an ALRM-signal to this process.
|
||||
#
|
||||
# Predefined alarm will be reset and will forget.
|
||||
# @note Older implementations of ffi-libc does not provide {alarm}, but we need it.
|
||||
# So we detect, if it is not provided and attach it.
|
||||
# @param seconds [0] Clears alarm.
|
||||
# @param seconds [Integer] How many seconds should be waited, before ALRM-signal should be send?
|
||||
# @return (nil)
|
||||
attach_function :alarm, [:uint], :uint unless FFI::LibC.respond_to? :alarm
|
||||
end
|
||||
end
|
||||
|
||||
# Helper module for `TimeoutInterrupt`
|
||||
# @see TimeoutInterrupt
|
||||
module TimeoutInterruptSingleton
|
||||
|
@ -51,7 +34,7 @@ module TimeoutInterruptSingleton
|
|||
# @return [nil]
|
||||
def raise_if_sb_timed_out
|
||||
return if self.timeouts.empty?
|
||||
key, (at, bt, exception) = self.timeouts.min_by {|key,(at,bt,ex)| at }
|
||||
_key, (at, bt, exception) = self.timeouts.min_by {|_key,(at,_bt,_ex)| at }
|
||||
return if Time.now < at
|
||||
raise exception, 'execution expired', bt
|
||||
end
|
||||
|
@ -66,7 +49,7 @@ module TimeoutInterruptSingleton
|
|||
else
|
||||
raise_if_sb_timed_out
|
||||
Signal.trap 'ALRM', &method( :alarm_trap)
|
||||
key, (at, bt) = timeouts.min_by {|key,(at,bt)| at }
|
||||
_key, (at, _bt) = timeouts.min_by {|_key,(at,_bt)| at }
|
||||
FFI::LibC.alarm (at - Time.now).to_i + 1
|
||||
end
|
||||
nil
|
||||
|
@ -76,9 +59,9 @@ module TimeoutInterruptSingleton
|
|||
#
|
||||
# @param seconds [0] No timeout, so block can take any time.
|
||||
# @param seconds [Integer] In `seconds` Seconds, it should raise a timeout, if not finished.
|
||||
# @param seconds [nil] If also no block given, everything will be ignored and
|
||||
# it will call {setup} for checking and preparing next known timeout.
|
||||
# @param exception [Exception] which will be raised if timed out.
|
||||
# @param seconds [nil] If this and no block given, it will call {setup} for checking and
|
||||
# preparing _next_ known timeout.
|
||||
# @param exception [exception] which exception will be raised if timed out?
|
||||
# @param exception [nil] `TimeoutInterrupt::Error` will be used to raise.
|
||||
# @param block [Proc] Will be called and should finish its work before it timed out.
|
||||
# @param block [nil] Nothing will happen, instead it will return a Proc,
|
||||
|
@ -87,7 +70,8 @@ module TimeoutInterruptSingleton
|
|||
# Or if not, it will return a Proc, which will expect a Proc if called.
|
||||
# This Proc has no arguments and will prepare a timeout, like if you had given a block.
|
||||
#
|
||||
# You can rescue `Timeout::Error`, instead `TimeoutInterrupt::Error`, it will work too.
|
||||
# You can rescue `Timeout::Error`, instead `TimeoutInterrupt::Error`,
|
||||
# it is a subclass of `Timeout::Error`.
|
||||
#
|
||||
# It will call your given block, which has `seconds` seconds to end.
|
||||
# If you want to prepare a timeout, which should be used many times,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
require 'rubygems'
|
||||
require 'bundler'
|
||||
|
||||
begin
|
||||
Bundler.setup(:default, :development)
|
||||
rescue Bundler::BundlerError => e
|
||||
|
@ -7,6 +8,7 @@ rescue Bundler::BundlerError => e
|
|||
$stderr.puts "Run `bundle install` to install missing gems"
|
||||
exit e.status_code
|
||||
end
|
||||
|
||||
require 'test/unit'
|
||||
require 'shoulda'
|
||||
|
||||
|
@ -14,9 +16,6 @@ require 'timeout'
|
|||
require 'benchmark'
|
||||
require 'ffi/libc'
|
||||
|
||||
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
||||
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
||||
|
||||
require 'timeout_interrupt'
|
||||
|
||||
class Test::Unit::TestCase
|
||||
|
|
|
@ -4,7 +4,7 @@ class TestRubyTimeoutInterrupt < Test::Unit::TestCase
|
|||
def blocking
|
||||
t = FFI::LibC.fopen '/dev/ptmx', 'r'
|
||||
b = FFI::LibC.malloc 1025
|
||||
s = FFI::LibC.fread b, 1, 1024, t
|
||||
FFI::LibC.fread b, 1, 1024, t
|
||||
ensure
|
||||
FFI::LibC.fclose t if t
|
||||
FFI::LibC.free b if b
|
||||
|
@ -114,7 +114,7 @@ class TestRubyTimeoutInterrupt < Test::Unit::TestCase
|
|||
called = false
|
||||
prepared.call do
|
||||
assert_raise TimeoutInterrupt::Error, 'It should time out after one second, but it did not.' do
|
||||
prepared.call { 2; sleep 2 }
|
||||
prepared.call { _ = 2; sleep 2 }
|
||||
end
|
||||
called = true
|
||||
end
|
||||
|
|
|
@ -1,71 +1,33 @@
|
|||
# Generated by jeweler
|
||||
# DO NOT EDIT THIS FILE DIRECTLY
|
||||
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
||||
# -*- encoding: utf-8 -*-
|
||||
Gem::Specification.new do |spec|
|
||||
spec.name = "timeout-interrupt"
|
||||
spec.version = "0.4.0"
|
||||
|
||||
Gem::Specification.new do |s|
|
||||
s.name = "timeout-interrupt"
|
||||
s.version = "0.3.0"
|
||||
spec.authors = ["Denis Knauf"]
|
||||
spec.description = "Timeout-lib, which interrupts everything, also systemcalls. It uses libc-alarm."
|
||||
spec.email = ["git+timeout-interrupt@denkn.at"]
|
||||
|
||||
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
||||
s.authors = ["Denis Knauf"]
|
||||
s.date = "2013-03-14"
|
||||
s.description = "Timeout-lib, which interrupts everything, also systemcalls. It uses libc-alarm."
|
||||
s.email = "Denis.Knauf@gmail.com"
|
||||
s.extra_rdoc_files = [
|
||||
"LICENSE.txt",
|
||||
"README.md"
|
||||
]
|
||||
s.files = [
|
||||
".document",
|
||||
"Gemfile",
|
||||
"Gemfile.lock",
|
||||
"LICENSE.txt",
|
||||
"README.md",
|
||||
"Rakefile",
|
||||
"VERSION",
|
||||
"lib/timeout_interrupt.rb",
|
||||
"test/helper.rb",
|
||||
"test/test_ruby-timeout-interrupt.rb",
|
||||
"timeout-interrupt.gemspec"
|
||||
]
|
||||
s.homepage = "http://github.com/DenisKnauf/ruby-timeout-interrupt"
|
||||
s.licenses = ["LGPLv3"]
|
||||
s.require_paths = ["lib"]
|
||||
s.rubygems_version = "1.8.11"
|
||||
s.summary = "\"Interrupts systemcalls too.\""
|
||||
spec.summary = "\"Interrupts systemcalls too.\""
|
||||
spec.licenses = ["LGPLv3"]
|
||||
|
||||
if s.respond_to? :specification_version then
|
||||
s.specification_version = 3
|
||||
spec.homepage = "https://git.denkn.at/deac/ruby-timeout-interrupt"
|
||||
spec.required_ruby_version = Gem::Requirement.new(">= 2.1.0")
|
||||
|
||||
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
||||
s.add_runtime_dependency(%q<ffi-libc>, [">= 0"])
|
||||
s.add_development_dependency(%q<shoulda>, [">= 0"])
|
||||
s.add_development_dependency(%q<yard>, [">= 0"])
|
||||
s.add_development_dependency(%q<redcarpet>, [">= 0"])
|
||||
s.add_development_dependency(%q<rdoc>, [">= 0"])
|
||||
s.add_development_dependency(%q<bundler>, [">= 0"])
|
||||
s.add_development_dependency(%q<jeweler>, [">= 0"])
|
||||
s.add_development_dependency(%q<simplecov>, [">= 0"])
|
||||
else
|
||||
s.add_dependency(%q<ffi-libc>, [">= 0"])
|
||||
s.add_dependency(%q<shoulda>, [">= 0"])
|
||||
s.add_dependency(%q<yard>, [">= 0"])
|
||||
s.add_dependency(%q<redcarpet>, [">= 0"])
|
||||
s.add_dependency(%q<rdoc>, [">= 0"])
|
||||
s.add_dependency(%q<bundler>, [">= 0"])
|
||||
s.add_dependency(%q<jeweler>, [">= 0"])
|
||||
s.add_dependency(%q<simplecov>, [">= 0"])
|
||||
end
|
||||
else
|
||||
s.add_dependency(%q<ffi-libc>, [">= 0"])
|
||||
s.add_dependency(%q<shoulda>, [">= 0"])
|
||||
s.add_dependency(%q<yard>, [">= 0"])
|
||||
s.add_dependency(%q<redcarpet>, [">= 0"])
|
||||
s.add_dependency(%q<rdoc>, [">= 0"])
|
||||
s.add_dependency(%q<bundler>, [">= 0"])
|
||||
s.add_dependency(%q<jeweler>, [">= 0"])
|
||||
s.add_dependency(%q<simplecov>, [">= 0"])
|
||||
end
|
||||
spec.metadata["homepage_uri"] = spec.homepage
|
||||
spec.metadata["source_code_uri"] = spec.homepage
|
||||
spec.metadata["changelog_uri"] = spec.homepage
|
||||
|
||||
# Specify which files should be added to the gem when it is released.
|
||||
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
||||
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
||||
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
||||
end
|
||||
spec.bindir = "bin"
|
||||
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
||||
spec.require_paths = ["lib"]
|
||||
|
||||
spec.add_runtime_dependency 'ffi-libc', '>= 0.1.1'
|
||||
spec.add_development_dependency 'test-unit'
|
||||
spec.add_development_dependency 'shoulda'
|
||||
spec.add_development_dependency 'rake'
|
||||
spec.add_development_dependency 'bundler'
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in New Issue