public
Rubygem
Description: Merb Core: All you need. None you don't.
Homepage: http://www.merbivore.com
Clone URL: git://github.com/wycats/merb-core.git
Tons of comments; support for various forking configurations
wycats (author)
Thu Sep 25 23:18:02 -0700 2008
commit  f4fd0d39ea76e1722ee115f15f307732603d7ce4
tree    9ec5a670f27e5c4a73ce8bd312fc795566d21427
parent  acb90a6e57e075717a235f5896df545937880416
...
392
393
394
 
 
395
396
397
398
399
400
 
 
401
402
403
...
421
422
423
424
 
 
 
 
425
426
427
...
465
466
467
 
 
 
 
 
 
 
 
468
469
470
...
491
492
493
494
495
496
497
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
498
499
500
...
518
519
520
521
 
522
523
524
...
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
...
425
426
427
 
428
429
430
431
432
433
434
...
472
473
474
475
476
477
478
479
480
481
482
483
484
485
...
506
507
508
 
 
 
 
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
...
550
551
552
 
553
554
555
556
0
@@ -392,12 +392,16 @@ class Merb::BootLoader::LoadClasses < Merb::BootLoader
0
       Merb::Controller.send :include, Merb::GlobalHelpers
0
     end
0
     
0
+    # Wait for any children to exit, remove the "main" PID, and
0
+    # exit.
0
     def exit_gracefully
0
       Process.waitall
0
       Merb::Server.remove_pid("main")
0
       exit
0
     end
0
     
0
+    # If using fork-based code reloading, set up the BEGIN
0
+    # point and set up any signals in the parent and child.
0
     def start_transaction
0
       Merb.logger.warn! "Parent pid: #{Process.pid}"
0
       reader, writer = nil, nil
0
@@ -421,7 +425,10 @@ class Merb::BootLoader::LoadClasses < Merb::BootLoader
0
         else
0
           trap("INT") do 
0
             Merb.logger.warn! "Killing children"
0
-            Process.kill("ABRT", pid)
0
+            begin
0
+              Process.kill("ABRT", pid)
0
+            rescue SystemCallError
0
+            end
0
             exit_gracefully
0
           end
0
         end
0
@@ -465,6 +472,14 @@ class Merb::BootLoader::LoadClasses < Merb::BootLoader
0
       end
0
     end
0
     
0
+    # Kill any children of the spawner process and exit with
0
+    # an appropriate status code.
0
+    #
0
+    # Note that exiting the spawner process with a status code
0
+    # of 128 when a master process exists will cause the
0
+    # spawner process to be recreated, and the app code reloaded.
0
+    #
0
+    # @param status<Integer> The status code to exit with
0
     def kill_children(status = 0)
0
       Merb.exiting = true unless status == 128
0
       
0
@@ -491,10 +506,27 @@ class Merb::BootLoader::LoadClasses < Merb::BootLoader
0
     # ==== Parameters
0
     # file<String>:: The file to load.
0
     def load_file(file)
0
-      klasses = ObjectSpace.classes.dup
0
-      load file
0
-      LOADED_CLASSES[file] = ObjectSpace.classes - klasses
0
-      MTIMES[file] = File.mtime(file)
0
+      # Don't do this expensive operation unless we need to
0
+      unless Merb::Config[:fork_for_class_load]
0
+        klasses = ObjectSpace.classes.dup
0
+      end
0
+      
0
+      # Ignore the file for syntax errors. The next time
0
+      # the file is changed, it'll be reloaded again
0
+      begin
0
+        load file
0
+      rescue SyntaxError
0
+        return
0
+      ensure
0
+        if Merb::Config[:reload_classes]
0
+          MTIMES[file] = File.mtime(file)
0
+        end
0
+      end
0
+      
0
+      # Don't do this expensive operation unless we need to
0
+      unless Merb::Config[:fork_for_class_load]
0
+        LOADED_CLASSES[file] = ObjectSpace.classes - klasses
0
+      end
0
     end
0
     
0
     # Load classes from given paths - using path/glob pattern.
0
@@ -518,7 +550,7 @@ class Merb::BootLoader::LoadClasses < Merb::BootLoader
0
     # ==== Parameters
0
     # file<String>:: The file to reload.
0
     def reload(file)
0
-      if Merb::Config[:fork_for_class_load]
0
+      if !Merb::Config[:fork_for_class_load]
0
         remove_classes_in_file(file) { |f| load_file(f) }
0
       else
0
         kill_children(128)
...
103
104
105
 
 
 
 
 
 
106
107
108
...
103
104
105
106
107
108
109
110
111
112
113
114
0
@@ -103,6 +103,12 @@ module Merb
0
       #   Configuration settings to use. These are merged with the defaults.
0
       def setup(settings = {})
0
         @configuration = defaults.merge(settings)
0
+        
0
+        unless @configuration[:reload_classes]
0
+          @configuration[:fork_for_class_load] = false
0
+        end
0
+        
0
+        @configuration
0
       end
0
 
0
       # Parses the command line arguments and stores them in the config.
...
2
3
4
 
5
6
7
 
 
8
9
10
 
 
11
12
 
13
14
15
 
16
17
18
...
24
25
26
 
 
27
28
29
...
31
32
33
 
 
 
34
35
36
37
38
39
40
41
 
 
42
43
 
44
45
46
47
48
49
 
50
 
 
 
51
52
 
 
 
 
53
54
55
 
56
57
58
59
60
61
 
 
62
63
64
...
68
69
70
 
 
 
71
72
73
74
75
76
 
 
77
78
79
80
 
 
81
82
83
84
85
86
 
87
88
89
90
91
92
 
93
94
 
95
96
 
 
 
 
 
 
97
98
 
 
99
100
101
 
 
102
103
104
...
117
118
119
 
120
121
122
 
123
124
125
...
2
3
4
5
6
 
 
7
8
9
 
 
10
11
12
 
13
14
15
16
17
18
19
20
...
26
27
28
29
30
31
32
33
...
35
36
37
38
39
40
41
42
43
44
45
46
 
 
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
...
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
 
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
...
155
156
157
158
159
160
161
162
163
164
165
0
@@ -2,17 +2,19 @@ module Merb
0
   module Rack
0
     class AbstractAdapter
0
     
0
+      # Spawn a new worker process at a port.
0
       def self.spawn_worker(port)
0
-        pid = Kernel.fork
0
-        start_at_port(port, @opts) unless pid
0
+        child_pid = Kernel.fork
0
+        start_at_port(port, @opts) unless child_pid
0
 
0
-        # pid means we're in the parent, which means continue the loop
0
-        throw(:new_worker) unless pid
0
+        # If we have a child_pid, we're in the parent. If we're
0
+        throw(:new_worker) unless child_pid
0
         
0
-        @pids[port] = pid
0
+        @pids[port] = child_pid
0
         $CHILDREN = @pids.values
0
       end
0
     
0
+      # The main start method for bootloaders that support forking.
0
       def self.start(opts={})
0
         @opts = opts
0
         $CHILDREN ||= []
0
@@ -24,6 +26,8 @@ module Merb
0
         
0
         Merb.logger.warn! "Cluster: #{max_port}"
0
         
0
+        # If we only have a single merb, just start it up and dispense with
0
+        # the spawner/worker setup.
0
         if max_port == 0
0
           start_at_port(port)
0
           return
0
@@ -31,34 +35,49 @@ module Merb
0
         
0
         $0 = "merb: spawner"
0
 
0
+        # For each port, spawn a new worker. The parent will continue in
0
+        # the loop, while the child will throw :new_worker and be booted
0
+        # out of the loop.
0
         catch(:new_worker) do
0
           0.upto(max_port) do |i|
0
             parent = spawn_worker(port + i)
0
           end
0
         end
0
 
0
-        # pid means we're in the parent, so start watching the children
0
-        # no pid means we're in a child, so just move on
0
+        # If we're in a worker, we're done. Otherwise, we've completed
0
+        # setting up workers and now need to watch them.
0
         return unless parent
0
 
0
+        # For each worker, set up a thread in the spawner to watch it
0
         0.upto(max_port) do |i|
0
           Thread.new do
0
             catch(:new_worker) do
0
               loop do
0
                 pid = @pids[port + i]
0
                 begin
0
+                  # Watch for the pid to exit.
0
                   _, status = Process.wait2(pid)
0
+                
0
+                # If the pid doesn't exist, we want to silently exit instead of
0
+                # raising here.
0
                 rescue SystemCallError => e
0
                 ensure
0
+                  # If there was no worker with that PID, the status was non-0
0
+                  # (we send back a status of 128 when ABRT is called on a 
0
+                  # child, and Merb.fatal! exits with a status of 1), or if
0
+                  # Merb is in the process of exiting, *then* don't respawn.
0
                   Thread.exit if !status || status.exitstatus != 0 || Merb.exiting
0
                 end
0
               
0
+                # Otherwise, respawn the worker, and watch it again.
0
                 spawn_worker(port + i)
0
               end
0
             end
0
           end
0
         end
0
 
0
+        # The spawner process will make it here, and when it does, it should just 
0
+        # sleep so it can pick up ctrl-c if it's in console mode.
0
         sleep
0
         
0
       end
0
@@ -68,37 +87,56 @@ module Merb
0
           Merb::Server.remove_pid(port)
0
         end
0
         
0
+        # If Merb is daemonized, trap INT. If it's not daemonized,
0
+        # we let the master process' ctrl-c control the cluster
0
+        # of workers.
0
         if Merb::Config[:daemonize]
0
           trap('INT') do
0
             stop
0
             Merb.logger.warn! "Exiting port #{port}\n"
0
             exit_process
0
           end
0
+        # If it was not fork_for_class_load, we already set up
0
+        # ctrl-c handlers in the master thread.
0
         elsif Merb::Config[:fork_for_class_load]
0
           trap('INT') { 1 }
0
         end
0
         
0
+        # In daemonized mode or not, support HUPing the process to
0
+        # restart it.
0
         trap('HUP') do
0
           stop
0
           Merb.logger.warn! "Exiting port #{port} on #{Process.pid}\n"
0
           exit_process
0
         end
0
         
0
+        # ABRTing the process will kill it, and it will not be respawned.
0
         trap('ABRT') do
0
           stopped = stop(128)
0
           Merb.logger.warn! "Exiting port #{port}\n" if stopped
0
           exit_process(128)
0
         end
0
         
0
+        # Each worker gets its own `ps' name.
0
         $0 = "merb: worker (port #{port})"
0
         
0
+        # Store the PID for this worker
0
         Merb::Server.store_pid(port)
0
-        Merb.logger = Merb::Logger.new(Merb.log_file(port), Merb::Config[:log_level], Merb::Config[:log_delimiter], Merb::Config[:log_auto_flush])
0
+        
0
+        # Set up the logger for this worker to point to its process.
0
+        Merb.logger = Merb::Logger.new(Merb.log_file(port), 
0
+          Merb::Config[:log_level], Merb::Config[:log_delimiter], 
0
+          Merb::Config[:log_auto_flush])
0
+          
0
         Merb.logger.warn!("Starting #{self.name.split("::").last} at port #{port}")
0
         
0
+        # If we can't connect to the port, keep trying until we can. Print
0
+        # a warning about this once. Try every 0.25s.
0
         printed_warning = false
0
         loop do
0
           begin
0
+            # Call the adapter's new_server method, which should attempt
0
+            # to bind to a port.
0
             new_server(port)
0
           rescue Errno::EADDRINUSE
0
             unless printed_warning
0
@@ -117,9 +155,11 @@ module Merb
0
         
0
         Merb::Server.change_privilege
0
         
0
+        # Call the adapter's start_server method.
0
         start_server
0
       end
0
       
0
+      # This can be overridden in adapters, but shouldn't need to be.
0
       def self.exit_process(status = 0)
0
         exit(status)
0
       end

Comments