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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3a512670034e9289a6697c10ad2c3cc15c25c53e
4
- data.tar.gz: 1bb81b98a09cc8d568803e76ecddd95cfcdfa3b6
3
+ metadata.gz: 81532c9c3fda9af1ddf09bf56ff2c479f507f279
4
+ data.tar.gz: aa68ce3727fa3dda1d21bb86771070b93ce5b595
5
5
  SHA512:
6
- metadata.gz: 0db33427235876fcd9d5f3f1c16ad38f3f0e30806febe59bce5da8d5516dbe7ee6c97163121b0f84de1c8e9fd8971d6cedd5bb1dbf27074b030c14fac90e5ab3
7
- data.tar.gz: 5084fb2c133abb9a083964ef84cb63d3e338c2d1008a8b3636aedfac3f7a03fc3efffc91f4543574efa4b83779db8c3ebff364dd05a2c9a2418e622a0aaa710b
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
@@ -60,7 +60,11 @@ else
60
60
 
61
61
  compress_javascript: Volt.env.production?,
62
62
  compress_css: Volt.env.production?,
63
- abort_on_exception: true
63
+ abort_on_exception: true,
64
+
65
+ min_worker_threads: 1,
66
+ max_worker_threads: 10,
67
+ worker_timeout: 60
64
68
  }
65
69
  end
66
70
 
@@ -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
- text += "\n" + colorize(error.backtrace.join("\n"), :red)
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 fromt he content are returned as additional headers
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
- Thread.current['meta'] = meta_data
40
- result = klass.new(@volt_app, channel, self).send(method_name, *args)
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
- Thread.current['meta'] = nil
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
@@ -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
@@ -1,5 +1,5 @@
1
1
  module Volt
2
2
  module Version
3
- STRING = '0.9.3.pre4'
3
+ STRING = '0.9.3.pre5'
4
4
  end
5
5
  end
data/lib/volt/volt/app.rb CHANGED
@@ -39,6 +39,7 @@ module Volt
39
39
  attr_reader :component_paths, :router, :page, :live_query_pool,
40
40
  :channel_live_queries, :app_path, :database, :message_bus,
41
41
  :middleware
42
+ attr_accessor :sprockets
42
43
 
43
44
  def initialize(app_path=nil)
44
45
  if Volt.server? && !app_path
@@ -51,6 +51,10 @@ unless RUBY_PLATFORM == 'opal'
51
51
  def handle_message(message)
52
52
  @receiver << message
53
53
  end
54
+
55
+ def still_alive?(*args)
56
+ false
57
+ end
54
58
  end
55
59
 
56
60
  # Disable for jruby for now
@@ -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
@@ -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
- spec.add_dependency 'concurrent-ruby', '~> 0.8.0'
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.pre4
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-05 00:00:00.000000000 Z
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