Class: Merb::BootLoader::LoadClasses

Inherits:
Merb::BootLoader show all
Defined in:
lib/merb-core/bootloader.rb

Overview

Load all classes inside the load paths.

This is used in conjunction with Merb::BootLoader::ReloadClasses to track files that need to be reloaded, and which constants need to be removed in order to reload a file.

This also adds the model, controller, and lib directories to the load path, so they can be required in order to avoid load-order issues.

Constant Summary collapse

LOADED_CLASSES =
{}
MTIMES =
{}

Class Method Summary collapse

Methods inherited from Merb::BootLoader

after, after_app_loads, before, before_app_loads, default_framework, finished?, inherited, move_klass

Class Method Details

.exit_gracefullyObject

Wait for any children to exit, remove the “main” PID, and exit.



410
411
412
413
414
# File 'lib/merb-core/bootloader.rb', line 410

def exit_gracefully
  Process.waitall
  Merb::Server.remove_pid("main")
  exit
end

.kill_children(status = 0) ⇒ Object

Kill any children of the spawner process and exit with an appropriate status code.

Note that exiting the spawner process with a status code of 128 when a master process exists will cause the spawner process to be recreated, and the app code reloaded.

Parameters:

  • status (Integer) (defaults to: 0)

    The status code to exit with



496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
# File 'lib/merb-core/bootloader.rb', line 496

def kill_children(status = 0)
  Merb.exiting = true unless status == 128
  
  begin
    @writer.puts(status.to_s) if @writer
  rescue SystemCallError
  end
  
  threads = []
  
  ($CHILDREN || []).each do |p|
    threads << Thread.new do
      begin
        Process.kill("ABRT", p)
        Process.wait2(p)
      rescue SystemCallError
      end
    end
  end
  threads.each {|t| t.join }
  exit(status)
end

.load_classes(*paths) ⇒ Object

Load classes from given paths - using path/glob pattern.

*paths<Array>

Array of paths to load classes from - may contain glob pattern



549
550
551
552
553
554
555
556
557
558
559
560
561
# File 'lib/merb-core/bootloader.rb', line 549

def load_classes(*paths)
  orphaned_classes = []
  paths.flatten.each do |path|
    Dir[path].each do |file|
      begin
        load_file file
      rescue NameError => ne
        orphaned_classes.unshift(file)
      end
    end
  end
  load_classes_with_requirements(orphaned_classes)
end

.load_file(file) ⇒ Object

Parameters

file<String>

The file to load.



521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
# File 'lib/merb-core/bootloader.rb', line 521

def load_file(file)
  # Don't do this expensive operation unless we need to
  unless Merb::Config[:fork_for_class_load]
    klasses = ObjectSpace.classes.dup
  end
  
  # Ignore the file for syntax errors. The next time
  # the file is changed, it'll be reloaded again
  begin
    load file
  rescue SyntaxError
    return
  ensure
    if Merb::Config[:reload_classes]
      MTIMES[file] = File.mtime(file)
    end
  end
  
  # Don't do this expensive operation unless we need to
  unless Merb::Config[:fork_for_class_load]
    LOADED_CLASSES[file] = ObjectSpace.classes - klasses
  end
end

.reload(file) ⇒ Object

Parameters

file<String>

The file to reload.



565
566
567
568
569
570
571
# File 'lib/merb-core/bootloader.rb', line 565

def reload(file)
  if !Merb::Config[:fork_for_class_load]
    remove_classes_in_file(file) { |f| load_file(f) }
  else
    kill_children(128)
  end
end

.reload_router!Object

Reload the router to regenerate all routes.



574
575
576
577
578
579
# File 'lib/merb-core/bootloader.rb', line 574

def reload_router!
  if File.file?(router_file = Merb.dir_for(:router) / Merb.glob_for(:router))
    Merb::Router.reset!
    reload router_file
  end
end

.remove_classes_in_file(file) {|file| ... } ⇒ Object

Parameters

file<String>

The file to remove classes for.

&block

A block to call with the file that has been removed.

Yields:

  • (file)


584
585
586
587
588
589
590
591
# File 'lib/merb-core/bootloader.rb', line 584

def remove_classes_in_file(file, &block)
  Merb.klass_hashes.each {|x| x.protect_keys!}
  if klasses = LOADED_CLASSES.delete(file)
    klasses.each { |klass| remove_constant(klass) unless klass.to_s =~ /Router/ }
  end
  yield file if block_given?
  Merb.klass_hashes.each {|x| x.unprotect_keys!}
end

.remove_constant(const) ⇒ Object

Parameters

const<Class>

The class to remove.



595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
# File 'lib/merb-core/bootloader.rb', line 595

def remove_constant(const)
  # This is to support superclasses (like AbstractController) that track
  # their subclasses in a class variable. Classes that wish to use this
  # functionality are required to alias it to _subclasses_list. Plugins
  # for ORMs and other libraries should keep this in mind.
  superklass = const
  until (superklass = superklass.superclass).nil?
    if superklass.respond_to?(:_subclasses_list)
      superklass.send(:_subclasses_list).delete(klass)
      superklass.send(:_subclasses_list).delete(klass.to_s)
    end
  end

  parts = const.to_s.split("::")
  base = parts.size == 1 ? Object : Object.full_const_get(parts[0..-2].join("::"))
  object = parts[-1].to_s
  begin
    base.send(:remove_const, object)
    Merb.logger.debug("Removed constant #{object} from #{base}")
  rescue NameError
    Merb.logger.debug("Failed to remove constant #{object} from #{base}")
  end
end

.runObject

Load all classes from Merb’s native load paths.



375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
# File 'lib/merb-core/bootloader.rb', line 375

def run
  # Add models, controllers, helpers and lib to the load path
  unless @ran
    $LOAD_PATH.unshift Merb.dir_for(:model)
    $LOAD_PATH.unshift Merb.dir_for(:controller)
    $LOAD_PATH.unshift Merb.dir_for(:lib)
    $LOAD_PATH.unshift Merb.dir_for(:helper)
  end

  @ran = true
  $0 = "merb: master"
  
  if Merb::Config[:fork_for_class_load] && Merb.env != "test"
    start_transaction
  else
    trap('INT') do 
      Merb.logger.warn! "Killing children"
      kill_children
    end
  end

  # Load application file if it exists - for flat applications
  load_file Merb.dir_for(:application) if File.file?(Merb.dir_for(:application))
  
  # Load classes and their requirements
  Merb.load_paths.each do |component, path|
    next unless path.last && component != :application
    load_classes(path.first / path.last)
  end

  Merb::Controller.send :include, Merb::GlobalHelpers
end

.start_transactionObject

If using fork-based code reloading, set up the BEGIN point and set up any signals in the parent and child.



418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
# File 'lib/merb-core/bootloader.rb', line 418

def start_transaction
  Merb.logger.warn! "Parent pid: #{Process.pid}"
  reader, writer = nil, nil
  
  if GC.respond_to?(:copy_on_write_friendly=)
    GC.copy_on_write_friendly = true
  end      
        
  loop do
    reader, @writer = IO.pipe
    pid = Kernel.fork
    
    # pid means we're in the parent; only stay in the loop in that case
    break unless pid
    @writer.close

    Merb::Server.store_pid("main")
    
    if Merb::Config[:console_trap]
      trap("INT") {}
    else
      trap("INT") do 
        Merb.logger.warn! "Killing children"
        begin
          Process.kill("ABRT", pid)
        rescue SystemCallError
        end
        exit_gracefully
      end
    end
    
    trap("HUP") do 
      Merb.logger.warn! "Doing a fast deploy\n"
      Process.kill("HUP", pid)
    end

    reader_ary = [reader]
    loop do
      if exit_status = Process.wait2(pid, Process::WNOHANG)
        exit_status[1] == 128 ? break : exit
      end
      if select(reader_ary, nil, nil, 0.25)
        begin
          next if reader.eof?
          msg = reader.readline
          if msg =~ /128/
            break
          else
            exit_gracefully
          end
        rescue SystemCallError
          exit_gracefully
        end
      end
    end
  end
 
  reader.close
 
  # add traps to the child
  if Merb::Config[:console_trap]
    Merb::Server.add_irb_trap
    at_exit { kill_children }
  else
    trap('INT') {}
    trap('ABRT') { kill_children }
    trap('HUP') { kill_children(128) }
  end
end