volt 0.9.3.pre4 → 0.9.3.pre5

Sign up to get free protection for your applications and to get access to all the features.
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