frugal_timeout 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/frugal_timeout.rb +130 -0
- metadata +61 -0
@@ -0,0 +1,130 @@
|
|
1
|
+
# Copyright (C) 2013 by Dmitry Maksyoma <ledestin@gmail.com>
|
2
|
+
|
3
|
+
require 'thread'
|
4
|
+
require 'timeout'
|
5
|
+
|
6
|
+
module FrugalTimeout
|
7
|
+
# {{{1 Error
|
8
|
+
class Error < Timeout::Error; end
|
9
|
+
|
10
|
+
# {{{1 Request
|
11
|
+
class Request
|
12
|
+
include Comparable
|
13
|
+
@@mutex = Mutex.new
|
14
|
+
|
15
|
+
attr_reader :at, :thread
|
16
|
+
|
17
|
+
def initialize thread, at
|
18
|
+
@thread, @at = thread, at
|
19
|
+
end
|
20
|
+
|
21
|
+
def <=>(other)
|
22
|
+
@at <=> other.at
|
23
|
+
end
|
24
|
+
|
25
|
+
def done!
|
26
|
+
@@mutex.synchronize { @done = true }
|
27
|
+
end
|
28
|
+
|
29
|
+
def done?
|
30
|
+
@@mutex.synchronize { @done }
|
31
|
+
end
|
32
|
+
|
33
|
+
def enforceTimeout
|
34
|
+
@thread.raise Error, 'execution expired' unless done?
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# {{{1 Main code
|
39
|
+
@in = Queue.new
|
40
|
+
|
41
|
+
# {{{2 Timeout request and expiration processing thread
|
42
|
+
Thread.new {
|
43
|
+
nearestTimeout, requests = nil, []
|
44
|
+
loop {
|
45
|
+
request = @in.shift
|
46
|
+
now = Time.now
|
47
|
+
|
48
|
+
if request == :expired
|
49
|
+
# Enforce all expired timeouts.
|
50
|
+
requests.sort!
|
51
|
+
requests.each_with_index { |r, i|
|
52
|
+
break if r.at > now
|
53
|
+
|
54
|
+
r.enforceTimeout
|
55
|
+
requests[i] = nil
|
56
|
+
}
|
57
|
+
requests.compact!
|
58
|
+
|
59
|
+
# Activate the nearest non-expired timeout.
|
60
|
+
nearestTimeout = unless requests.first
|
61
|
+
nil
|
62
|
+
else
|
63
|
+
setupSleeper requests.first.at - now
|
64
|
+
requests.first.at
|
65
|
+
end
|
66
|
+
|
67
|
+
next
|
68
|
+
end
|
69
|
+
|
70
|
+
# New timeout request.
|
71
|
+
# Already expired, enforce right away.
|
72
|
+
if request.at <= now
|
73
|
+
request.enforceTimeout
|
74
|
+
next
|
75
|
+
end
|
76
|
+
|
77
|
+
# Queue new timeout for later enforcing. Activate if it's nearest to
|
78
|
+
# enforce.
|
79
|
+
requests << request
|
80
|
+
next if nearestTimeout && request.at > nearestTimeout
|
81
|
+
|
82
|
+
setupSleeper request.at - now
|
83
|
+
nearestTimeout = request.at
|
84
|
+
}
|
85
|
+
}
|
86
|
+
|
87
|
+
# {{{2 Closest expiration notifier thread
|
88
|
+
@sleeperDelays, @sleeperMutex = Queue.new, Mutex.new
|
89
|
+
@sleeper = Thread.new {
|
90
|
+
loop {
|
91
|
+
sleepFor = nil
|
92
|
+
@sleeperMutex.synchronize {
|
93
|
+
sleepFor = @sleeperDelays.shift until @sleeperDelays.empty?
|
94
|
+
}
|
95
|
+
start = Time.now
|
96
|
+
sleepFor ? sleep(sleepFor) : sleep
|
97
|
+
slept = Time.now - start
|
98
|
+
@sleeperMutex.synchronize {
|
99
|
+
@in.push :expired if sleepFor && slept >= sleepFor
|
100
|
+
}
|
101
|
+
}
|
102
|
+
}
|
103
|
+
|
104
|
+
# {{{2 Methods
|
105
|
+
# Replace Object.timeout().
|
106
|
+
def self.dropin!
|
107
|
+
Object.class_eval \
|
108
|
+
'def timeout t, klass=nil, &b
|
109
|
+
FrugalTimeout.timeout t, klass, &b
|
110
|
+
end'
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.setupSleeper sleepFor
|
114
|
+
@sleeperMutex.synchronize {
|
115
|
+
sleep 0.1 until @sleeper.status == 'sleep'
|
116
|
+
@sleeperDelays.push sleepFor
|
117
|
+
@sleeper.wakeup
|
118
|
+
}
|
119
|
+
end
|
120
|
+
|
121
|
+
def self.timeout t, klass=nil
|
122
|
+
@in.push request = Request.new(Thread.current, Time.now + t)
|
123
|
+
begin
|
124
|
+
yield t
|
125
|
+
ensure
|
126
|
+
request.done! unless $!.is_a? FrugalTimeout::Error
|
127
|
+
end
|
128
|
+
end
|
129
|
+
#}}}1
|
130
|
+
end
|
metadata
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: frugal_timeout
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Dmitry Maksyoma
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-07-16 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '2.13'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '2.13'
|
30
|
+
description: Timeout.timeout replacement that uses only 2 threads
|
31
|
+
email: ledestin@gmail.com
|
32
|
+
executables: []
|
33
|
+
extensions: []
|
34
|
+
extra_rdoc_files: []
|
35
|
+
files:
|
36
|
+
- lib/frugal_timeout.rb
|
37
|
+
homepage: http://rubygems.org/gems/frugal_timeout
|
38
|
+
licenses: []
|
39
|
+
post_install_message:
|
40
|
+
rdoc_options: []
|
41
|
+
require_paths:
|
42
|
+
- lib
|
43
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
44
|
+
none: false
|
45
|
+
requirements:
|
46
|
+
- - ! '>='
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
requirements: []
|
56
|
+
rubyforge_project:
|
57
|
+
rubygems_version: 1.8.23
|
58
|
+
signing_key:
|
59
|
+
specification_version: 3
|
60
|
+
summary: Timeout.timeout replacement
|
61
|
+
test_files: []
|