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.
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: []