Compare commits

...

18 Commits

Author SHA1 Message Date
Denis Knauf f963e41c52 „README.md“ ändern 2022-03-24 12:45:07 +01:00
Denis Knauf 5b390b2083 reimplement tests and minor fixes. 2021-12-12 18:19:34 +01:00
Denis Knauf 3542837d7f small minor warnings fixed (unused vars, ...). comments. 2021-12-12 18:18:33 +01:00
Denis Knauf 28936187de dependencies: need should, test-unit for testing. but no rspec. 2021-12-12 18:16:14 +01:00
Denis Knauf 486cf53495 .document removed and ignored 2021-12-12 18:14:31 +01:00
Denis Knauf ae8f1e0a33 Gemfile.lock removed. Ignore this file anyway 2021-12-12 18:13:21 +01:00
Denis Knauf 3ca2de0cd9 email added 2021-12-12 16:11:03 +01:00
Denis Knauf 9fc25c3015 Actiual versions of ffi-libc provide FFI::LibC::alarm. ffi-libc<0.1.1 has a bug with sys_errlist, so 0.1.1 required. 2021-12-12 16:09:49 +01:00
Denis Knauf f27600ce16 Gemfile updated, Rakefile/gemspec modernized. 2021-12-12 15:59:25 +01:00
Denis Knauf 8698a3b01d Regenerate gemspec for version 0.3.0 2013-03-14 16:26:43 +01:00
Denis Knauf 2f108b5724 Version bump to 0.3.0 2013-03-14 16:26:28 +01:00
Denis Knauf 9f993da845 timeout(0) -> never timeout (compatible to Timeout) 2013-03-14 16:26:07 +01:00
Denis Knauf 0a0f47650e Regenerate gemspec for version 0.2.2 2013-03-07 14:50:29 +01:00
Denis Knauf 4c61e68354 Version bump to 0.2.2 2013-03-07 14:50:18 +01:00
Denis Knauf ddee6b499f License changed (LICENSE.txt was LGPLv3, but Gem not) 2013-03-07 14:50:05 +01:00
Denis Knauf 30ec1b45d4 Regenerate gemspec for version 0.2.1 2013-03-07 14:48:18 +01:00
Denis Knauf 6ab9ad2d13 Version bump to 0.2.1 2013-03-07 14:48:03 +01:00
Denis Knauf ae0409111a :doc:. alarm_trap will call raise_if_sb_timed_out and setup. raise_if_sb_timed_out is the old alarm_trap (but alarm_trap should be used like before). setup recognize time outs lesser than one second now. 2013-03-07 14:47:48 +01:00
11 changed files with 205 additions and 243 deletions

View File

@ -1,5 +0,0 @@
lib/**/*.rb
bin/*
-
features/**/*.feature
LICENSE.txt

37
.gitignore vendored
View File

@ -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

16
Gemfile
View File

@ -1,14 +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 "rdoc"
gem "bundler"
gem "jeweler"
gem "simplecov"
end
source "https://rubygems.org"
gemspec

View File

@ -1,50 +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)
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
shoulda
simplecov
yard

View File

@ -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.

View File

@ -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 = "AGPLv3"
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

View File

@ -1 +1 @@
0.2.0
0.3.0

View File

@ -1,40 +1,93 @@
require 'ffi/libc'
require 'timeout'
module FFI
module LibC
attach_function :alarm, [:uint], :uint unless FFI::LibC.respond_to? :alarm
end
end
# Helper module for `TimeoutInterrupt`
# @see TimeoutInterrupt
module TimeoutInterruptSingleton
class <<self
# Stores all timeouts.
#
# @param thread [nil] must be nil! Do not use it yet!
# @return [Hash< key(Integer): [at(Time), backtrace(Array<String>), exception(Exception)] >]
def timeouts thread = nil
@timeouts ||= Hash.new {|h,k| h[k] = [] }
thread = Thread.current if thread.kind_of? Thread
@timeouts ||= Hash.new {|h,k| h[k] = {} }
thread = Thread.current unless thread.kind_of? Thread
thread ? @timeouts[thread] : @timeouts
end
# If there's a timed out timeout, it will raise its exception.
# Can be used for handling ALRM-signal.
# It will prepare the next timeout, too.
#
# The timeout will not removed from timeouts, because it is timed out, yet.
# First, if timeout-scope will be exit, it will be removed.
#
# @return [nil]
def alarm_trap sig
key, (at, bt, exception) = self.timeouts.min_by {|key,(at,bt,ex)| at }
raise_if_sb_timed_out
setup
end
# There is a timed out timeout? It will raise it!
# You need not to check it yourself, it will do it for you.
#
# @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 }
return if Time.now < at
raise exception, 'execution expired', bt
end
# Prepares the next timeout. Sets the trap and the shortest timeout as alarm.
#
# @return [nil]
def setup
if timeouts.empty?
Signal.trap( 'ALRM') {}
FFI::LibC.alarm 0
else
key, (at, bt) = timeouts.min_by {|key,(at,bt)| at }
secs = (at - Time.now)
alarm_trap 14 if 0 > secs
raise_if_sb_timed_out
Signal.trap 'ALRM', &method( :alarm_trap)
FFI::LibC.alarm secs.to_i+1
_key, (at, _bt) = timeouts.min_by {|_key,(at,_bt)| at }
FFI::LibC.alarm (at - Time.now).to_i + 1
end
nil
end
def timeout seconds = nil, exception = nil
# Creates a timeout and calls your block, which has to finish before timeout occurs.
#
# @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 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,
# which can be called with a block to use the timeout.
# @return If block given, the returned value of your block.
# 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 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,
# without giving `seconds` and `exception`, you can omit the block,
# so, `TimeoutInterruptSingleton#timeout` will return a `Proc`, which want to have the block.
#
# There is a problem with scoped timeouts. If you rescue a timeout in an other timeout,
# it's possible, that the other timeout will never timeout, because both are timed out at once.
# Than you need to call `TimeoutInterruptSingleton#timeout` without arguments.
# It will prepare the next timeout or it will raise it directy, if timed out.
#
# @see TimeoutInterrupt.timeout
# @see TimeoutInterrupt#timeout
# @raise exception
def timeout seconds = nil, exception = nil, &block
return yield( seconds) if seconds.nil? || 0 == seconds if block_given?
return setup if seconds.nil?
seconds = seconds.to_i
exception ||= TimeoutInterrupt::Error
@ -50,7 +103,7 @@ module TimeoutInterruptSingleton
begin
self.timeouts[key] = [at, bt, exception]
setup
yield
yield seconds
ensure
self.timeouts.delete key
setup
@ -59,15 +112,84 @@ module TimeoutInterruptSingleton
end
end
# Can be included, or used directly.
# In both cases, it provides {#timeout}.
#
# @see TimeoutInterruptSingleton
module TimeoutInterrupt
# The {TimeoutInterrupt::Error} is the default exception, which will be raised,
# if something will time out.
# Its base-class is {Timeout::Error}, so you can replace {Timeout} by {TimeoutInterrupt} without
# replacing your `rescue Timeout::Error`, but you can.
class Error < Timeout::Error
end
def self.timeout seconds = nil, exception = nil, &e
TimeoutInterruptSingleton.timeout seconds, exception, &e
# Creates a timeout and calls your block, which has to finish before timeout occurs.
#
# @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 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,
# which can be called with a block to use the timeout.
# @return If block given, the returned value of your block.
# 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.
#
# 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,
# without giving `seconds` and `exception`, you can omit the block,
# so, `TimeoutInterruptSingleton#timeout` will return a `Proc`, which want to have the block.
#
# There is a problem with scoped timeouts. If you rescue a timeout in an other timeout,
# it's possible, that the other timeout will never timeout, because both are timed out at once.
# Than you need to call `TimeoutInterruptSingleton#timeout` without arguments.
# It will prepare the next timeout or it will raise it directy, if timed out.
#
# @see TimeoutInterrupt#timeout
# @see TimeoutInterruptSingleton.timeout
# @raise exception
def self.timeout seconds = nil, exception = nil, &block
TimeoutInterruptSingleton.timeout seconds, exception, &block
end
def timeout seconds = nil, exception = nil, &e
TimeoutInterruptSingleton.timeout seconds, exception, &e
# Creates a timeout and calls your block, which has to finish before timeout occurs.
#
# @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 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,
# which can be called with a block to use the timeout.
# @return If block given, the returned value of your block.
# 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.
#
# 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,
# without giving `seconds` and `exception`, you can omit the block,
# so, `TimeoutInterruptSingleton#timeout` will return a `Proc`, which want to have the block.
#
# There is a problem with scoped timeouts. If you rescue a timeout in an other timeout,
# it's possible, that the other timeout will never timeout, because both are timed out at once.
# Than you need to call `TimeoutInterruptSingleton#timeout` without arguments.
# It will prepare the next timeout or it will raise it directy, if timed out.
#
# @note This method is useful, if you `include TimeoutInterrupt`. You can call it directly.
# @see TimeoutInterrupt.timeout
# @see TimeoutInterruptSingleton.timeout
# @raise exception
def timeout seconds = nil, exception = nil, &block
TimeoutInterruptSingleton.timeout seconds, exception, &block
end
end

View File

@ -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

View File

@ -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
@ -152,4 +152,11 @@ class TestRubyTimeoutInterrupt < Test::Unit::TestCase
end
end
end
should "not timeout, if timeout is 0" do
assert_nothing_raised TimeoutInterrupt::Error, "Unexpected Timed out." do
# should never timeout (we can not wait infinity seconds, so only 5)
TimeoutInterrupt.timeout( 0) { sleep 5 }
end
end
end

View File

@ -1,68 +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.2.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-07"
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 = ["AGPLv3"]
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<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<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<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