volt 0.9.3.pre4 → 0.9.3.pre5
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1 -0
- data/README.md +1 -0
- data/lib/volt/config.rb +5 -1
- data/lib/volt/extra_core/logger.rb +9 -1
- data/lib/volt/server/middleware/default_middleware_stack.rb +1 -0
- data/lib/volt/server/rack/http_response_renderer.rb +1 -1
- data/lib/volt/tasks/dispatcher.rb +68 -26
- data/lib/volt/tasks/task.rb +8 -0
- data/lib/volt/version.rb +1 -1
- data/lib/volt/volt/app.rb +1 -0
- data/spec/server/message_bus/peer_to_peer/peer_connection_spec.rb +4 -0
- data/spec/tasks/dispatcher_spec.rb +9 -2
- data/templates/newgem/app/newgem/models/.empty_directory +0 -0
- data/templates/project/config/app.rb.tt +15 -0
- data/volt.gemspec +4 -1
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 81532c9c3fda9af1ddf09bf56ff2c479f507f279
|
4
|
+
data.tar.gz: aa68ce3727fa3dda1d21bb86771070b93ce5b595
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9079b50cbc1bf729c3c56a11fc30082edf7c846628fccc4cd8b468a5c7184f14c7c25dbcc16dc60ab6ad4a7c51e54ec3154a97c8a38aa8c313bffea7accc071d
|
7
|
+
data.tar.gz: 88abd17e264363c606421fbffa52621f01bc869147de950b0922b796ad7ff74da5a0f9b04e6a2aedb79adebf957f07da7af9807c329317c12e3f9638cadae1d8
|
data/CHANGELOG.md
CHANGED
@@ -18,6 +18,7 @@
|
|
18
18
|
- .inspect for models is now cleaner
|
19
19
|
- Volt.current_user now works in HttpController's
|
20
20
|
- You can now add your own middleware to the middleware stack. (see docs)
|
21
|
+
- Added a threadpool for Tasks, and options to customize pool size in config/app.rb
|
21
22
|
|
22
23
|
### Changed
|
23
24
|
- All methods on ArrayModel's under the store collection now return a Promise.
|
data/README.md
CHANGED
@@ -16,6 +16,7 @@ Pages HTML is written in a template language where you can put Ruby between `{{`
|
|
16
16
|
See some demo videos here:
|
17
17
|
- [Volt Todos Example](https://www.youtube.com/watch?v=KbFtIt7-ge8)
|
18
18
|
- [What Is Volt in 6 Minutes](https://www.youtube.com/watch?v=P27EPQ4ne7o)
|
19
|
+
- [Promises in 0.9.3 prerelease](https://www.youtube.com/watch?v=1RX9i8ivtWI)
|
19
20
|
- [Pagination Example](https://www.youtube.com/watch?v=1uanfzMLP9g)
|
20
21
|
- [Routes and Templates](https://www.youtube.com/watch?v=1yNMP3XR6jU)
|
21
22
|
- [Isomorphic App Development - RubyConf 2014](https://www.youtube.com/watch?v=7i6AL7Walc4)
|
data/lib/volt/config.rb
CHANGED
@@ -41,7 +41,10 @@ else
|
|
41
41
|
if error
|
42
42
|
text += "\n" + colorize(error.to_s, :red)
|
43
43
|
if error.is_a?(Exception) && !error.is_a?(VoltUserError)
|
44
|
-
|
44
|
+
backtrace = error.try(:backtrace)
|
45
|
+
if backtrace
|
46
|
+
text += "\n" + colorize(error.backtrace.join("\n"), :red)
|
47
|
+
end
|
45
48
|
end
|
46
49
|
end
|
47
50
|
|
@@ -68,6 +71,11 @@ else
|
|
68
71
|
Volt.logger.info(colorize(msg, color))
|
69
72
|
end
|
70
73
|
|
74
|
+
def error(msg)
|
75
|
+
msg ||= yield
|
76
|
+
super(colorize(msg, :red))
|
77
|
+
end
|
78
|
+
|
71
79
|
private
|
72
80
|
|
73
81
|
def colorize(string, color)
|
@@ -46,6 +46,7 @@ module Volt
|
|
46
46
|
|
47
47
|
# Serve the opal files
|
48
48
|
opal_files = OpalFiles.new(rack_app, volt_app.app_path, volt_app.component_paths)
|
49
|
+
volt_app.sprockets = opal_files.environment
|
49
50
|
|
50
51
|
# Serve the main html files from public, also figure out
|
51
52
|
# which JS/CSS files to serve.
|
@@ -21,7 +21,7 @@ module Volt
|
|
21
21
|
|
22
22
|
# Iterate through @renderes to find a matching renderer for the given
|
23
23
|
# content and call the given proc.
|
24
|
-
# Other params
|
24
|
+
# Other params from the content are returned as additional headers
|
25
25
|
# Returns an empty string if no renderer could be found
|
26
26
|
def render(content)
|
27
27
|
content = content.symbolize_keys
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# require 'ruby-prof'
|
2
2
|
require 'volt/utils/logging/task_logger'
|
3
3
|
require 'drb'
|
4
|
+
require 'concurrent'
|
4
5
|
|
5
6
|
module Volt
|
6
7
|
# The task dispatcher is responsible for taking incoming messages
|
@@ -13,12 +14,69 @@ module Volt
|
|
13
14
|
|
14
15
|
def initialize(volt_app)
|
15
16
|
@volt_app = volt_app
|
17
|
+
|
18
|
+
if Volt.env.test?
|
19
|
+
# When testing, we want to run immediately so it blocks and doesn't
|
20
|
+
# start the next thread.
|
21
|
+
@worker_pool = Concurrent::ImmediateExecutor.new
|
22
|
+
else
|
23
|
+
@worker_pool = Concurrent::ThreadPoolExecutor.new(
|
24
|
+
min_threads: Volt.config.min_worker_threads,
|
25
|
+
max_threads: Volt.config.max_worker_threads
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
@worker_timeout = Volt.config.worker_timeout || 60
|
16
30
|
end
|
17
31
|
|
18
32
|
# Dispatch takes an incoming Task from the client and runs it on the
|
19
33
|
# server, returning the result to the client.
|
20
34
|
# Tasks returning a promise will wait to return.
|
21
35
|
def dispatch(channel, message)
|
36
|
+
# Dispatch the task in the worker pool. Pas in the meta data
|
37
|
+
@worker_pool.post do
|
38
|
+
begin
|
39
|
+
dispatch_in_thread(channel, message)
|
40
|
+
rescue => e
|
41
|
+
err = "Worker Thread Exception for #{message}\n"
|
42
|
+
err += e.inspect
|
43
|
+
err += e.backtrace.join("\n") if e.respond_to?(:backtrace)
|
44
|
+
|
45
|
+
Volt.logger.error(err)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
# Check if it is safe to use this method
|
52
|
+
def safe_method?(klass, method_name)
|
53
|
+
# Make sure the class being called is a Task.
|
54
|
+
return false unless klass.ancestors.include?(Task)
|
55
|
+
|
56
|
+
# Make sure the method is defined on the klass we're using and not up the hiearchy.
|
57
|
+
# ^ This check prevents methods like #send, #eval, #instance_eval, #class_eval, etc...
|
58
|
+
klass.ancestors.each do |ancestor_klass|
|
59
|
+
if ancestor_klass.instance_methods(false).include?(method_name)
|
60
|
+
return true
|
61
|
+
elsif ancestor_klass == Task
|
62
|
+
# We made it to Task and didn't find the method, that means it
|
63
|
+
# was defined above Task, so we reject the call.
|
64
|
+
return false
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
false
|
69
|
+
end
|
70
|
+
|
71
|
+
def close_channel(channel)
|
72
|
+
QueryTasks.new(@volt_app, channel).close!
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
# Do the actual dispatching, should be running inside of a worker thread at
|
78
|
+
# this point.
|
79
|
+
def dispatch_in_thread(channel, message)
|
22
80
|
callback_id, class_name, method_name, meta_data, *args = message
|
23
81
|
method_name = method_name.to_sym
|
24
82
|
|
@@ -36,10 +94,13 @@ module Volt
|
|
36
94
|
|
37
95
|
# Init and send the method
|
38
96
|
promise = promise.then do
|
39
|
-
|
40
|
-
|
97
|
+
result = nil
|
98
|
+
Concurrent.timeout(klass.__timeout || @worker_timeout) do
|
99
|
+
Thread.current['meta'] = meta_data
|
100
|
+
result = klass.new(@volt_app, channel, self).send(method_name, *args)
|
41
101
|
|
42
|
-
|
102
|
+
Thread.current['meta'] = nil
|
103
|
+
end
|
43
104
|
|
44
105
|
result
|
45
106
|
end
|
@@ -51,6 +112,10 @@ module Volt
|
|
51
112
|
|
52
113
|
# Called after task runs or fails
|
53
114
|
finish = proc do |error|
|
115
|
+
if error.is_a?(Concurrent::TimeoutError)
|
116
|
+
error = Concurrent::TimeoutError.new("Task Timed Out after #{@worker_timeout} seconds: #{message}")
|
117
|
+
end
|
118
|
+
|
54
119
|
run_time = ((Time.now.to_f - start_time) * 1000).round(3)
|
55
120
|
Volt.logger.log_dispatch(class_name, method_name, run_time, args, error)
|
56
121
|
end
|
@@ -64,30 +129,7 @@ module Volt
|
|
64
129
|
finish.call(error)
|
65
130
|
channel.send_message('response', callback_id, nil, error)
|
66
131
|
end
|
67
|
-
end
|
68
132
|
|
69
|
-
# Check if it is safe to use this method
|
70
|
-
def safe_method?(klass, method_name)
|
71
|
-
# Make sure the class being called is a Task.
|
72
|
-
return false unless klass.ancestors.include?(Task)
|
73
|
-
|
74
|
-
# Make sure the method is defined on the klass we're using and not up the hiearchy.
|
75
|
-
# ^ This check prevents methods like #send, #eval, #instance_eval, #class_eval, etc...
|
76
|
-
klass.ancestors.each do |ancestor_klass|
|
77
|
-
if ancestor_klass.instance_methods(false).include?(method_name)
|
78
|
-
return true
|
79
|
-
elsif ancestor_klass == Task
|
80
|
-
# We made it to Task and didn't find the method, that means it
|
81
|
-
# was defined above Task, so we reject the call.
|
82
|
-
return false
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
false
|
87
|
-
end
|
88
|
-
|
89
|
-
def close_channel(channel)
|
90
|
-
QueryTasks.new(@volt_app, channel).close!
|
91
133
|
end
|
92
134
|
end
|
93
135
|
end
|
data/lib/volt/tasks/task.rb
CHANGED
@@ -18,6 +18,8 @@ module Volt
|
|
18
18
|
else
|
19
19
|
include CollectionHelpers
|
20
20
|
|
21
|
+
class_attribute :__timeout
|
22
|
+
|
21
23
|
def initialize(volt_app, channel = nil, dispatcher = nil)
|
22
24
|
@volt_app = volt_app
|
23
25
|
@page = volt_app.page
|
@@ -34,6 +36,12 @@ module Volt
|
|
34
36
|
@subclasses ||= []
|
35
37
|
end
|
36
38
|
|
39
|
+
# Set the timeout for method calls on this task. (The default is
|
40
|
+
# Volt.config.worker_timeout)
|
41
|
+
def timeout(value)
|
42
|
+
self.__timeout = value
|
43
|
+
end
|
44
|
+
|
37
45
|
# On the backend, we proxy all class methods like we would
|
38
46
|
# on the front-end. This returns a promise, even if the
|
39
47
|
# original code did not.
|
data/lib/volt/version.rb
CHANGED
data/lib/volt/volt/app.rb
CHANGED
@@ -7,13 +7,20 @@ if RUBY_PLATFORM != 'opal'
|
|
7
7
|
end
|
8
8
|
end
|
9
9
|
|
10
|
+
class WorkerPoolStub
|
11
|
+
def post(*args)
|
12
|
+
yield(*args)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
10
16
|
describe Volt::Dispatcher do
|
17
|
+
let(:dispatcher) { Volt::Dispatcher.new(Volt.current_app) }
|
18
|
+
|
11
19
|
before do
|
12
20
|
Volt.logger = spy('Volt::VoltLogger')
|
21
|
+
allow(Concurrent::ThreadPoolExecutor).to receive(:new).and_return(WorkerPoolStub.new)
|
13
22
|
end
|
14
23
|
|
15
|
-
let(:dispatcher) { Volt::Dispatcher.new(Volt.current_app) }
|
16
|
-
|
17
24
|
after do
|
18
25
|
# Cleanup, make volt make a new logger. Otherwise this will leak out.
|
19
26
|
Volt.logger = nil
|
File without changes
|
@@ -114,4 +114,19 @@ Volt.configure do |config|
|
|
114
114
|
#
|
115
115
|
# Bind Ip - specifies the ip address the message bus server should bind on.
|
116
116
|
# config.message_bus.bind_ip = '127.0.0.1'
|
117
|
+
|
118
|
+
#############
|
119
|
+
# Concurrency
|
120
|
+
#############
|
121
|
+
# Volt provides a thread worker pool for incoming task requests (and all
|
122
|
+
# database requests, since those use tasks to do their work.) The following
|
123
|
+
# lets you control the size of the worker pool. Threads are only created as
|
124
|
+
# needed, and are removed after a certain amount of inactivity.
|
125
|
+
# config.min_worker_threads = 1
|
126
|
+
# config.max_worker_threads = 10
|
127
|
+
#
|
128
|
+
# You can also specify the amount of time a Task should run for before it
|
129
|
+
# timeout's. Setting this to short can cause unexpected results, currently
|
130
|
+
# we recomend it be at least 10 seconds.
|
131
|
+
# config.worker_timeout = 60
|
117
132
|
end
|
data/volt.gemspec
CHANGED
@@ -29,7 +29,10 @@ Gem::Specification.new do |spec|
|
|
29
29
|
spec.add_dependency 'opal', '~> 0.7.2'
|
30
30
|
spec.add_dependency 'bundler', '>= 1.5'
|
31
31
|
spec.add_dependency 'faye-websocket', '~> 0.9.2'
|
32
|
-
|
32
|
+
|
33
|
+
# Locking down concurrent-ruby because one currently used feature is going to
|
34
|
+
# be deprecated (which we need to build a work around for)
|
35
|
+
spec.add_dependency 'concurrent-ruby', '= 0.8.0'
|
33
36
|
|
34
37
|
|
35
38
|
# For user passwords
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: volt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.3.
|
4
|
+
version: 0.9.3.pre5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Stout
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-06-
|
11
|
+
date: 2015-06-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -154,14 +154,14 @@ dependencies:
|
|
154
154
|
name: concurrent-ruby
|
155
155
|
requirement: !ruby/object:Gem::Requirement
|
156
156
|
requirements:
|
157
|
-
- -
|
157
|
+
- - '='
|
158
158
|
- !ruby/object:Gem::Version
|
159
159
|
version: 0.8.0
|
160
160
|
type: :runtime
|
161
161
|
prerelease: false
|
162
162
|
version_requirements: !ruby/object:Gem::Requirement
|
163
163
|
requirements:
|
164
|
-
- -
|
164
|
+
- - '='
|
165
165
|
- !ruby/object:Gem::Version
|
166
166
|
version: 0.8.0
|
167
167
|
- !ruby/object:Gem::Dependency
|
@@ -764,6 +764,7 @@ files:
|
|
764
764
|
- templates/newgem/app/newgem/controllers/main_controller.rb.tt
|
765
765
|
- templates/newgem/app/newgem/controllers/server/.empty_directory
|
766
766
|
- templates/newgem/app/newgem/lib/.empty_directory
|
767
|
+
- templates/newgem/app/newgem/models/.empty_directory
|
767
768
|
- templates/newgem/app/newgem/views/main/index.html
|
768
769
|
- templates/newgem/bin/newgem.tt
|
769
770
|
- templates/newgem/gitignore.tt
|