frugal_timeout 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (2) hide show
  1. data/lib/frugal_timeout.rb +130 -0
  2. 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: []