in-parallel 0.1.9 → 0.1.10
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/README.md +41 -32
- data/in_parallel/version.rb +1 -1
- data/lib/in_parallel.rb +121 -81
- data/lib/parallel_enumerable.rb +9 -10
- data/lib/parallel_logger.rb +13 -0
- metadata +3 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 616a0cfcbc49b019d69c306e9e919a86efdd9fee
         | 
| 4 | 
            +
              data.tar.gz: 4e7aba20c6ca158d124242c74ce44ca59fbf9179
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: d9e961955959052dd40268155c142deefa2950145519d9ba1d778ac18c7dfcf256e66328ec55ef08c1fb0e10ad41a81760154e86f986240082055a2c024b3363
         | 
| 7 | 
            +
              data.tar.gz: 03ea21bf0625a57e368595faf8bbdf8a5bfed798a4663e43e92da26f89e793b897b1a175824f61a355e84158a3abde04bae7fba10a35bbbecdc8772345e757a3
         | 
    
        data/README.md
    CHANGED
    
    | @@ -1,19 +1,20 @@ | |
| 1 1 | 
             
            # in-parallel
         | 
| 2 2 | 
             
            A lightweight Ruby library with very simple syntax, making use of process.fork for parallelization
         | 
| 3 3 |  | 
| 4 | 
            -
            Other popular Ruby  | 
| 4 | 
            +
            Other popular Ruby libraries that do parallel execution support one primary use case - crunching through a large queue of small tasks as quickly and efficiently as possible.  This library primarily supports the use case of executing a few larger tasks in parallel and managing the stdout and return values to make it easy to understand which processes are logging what, and what the outcome of the execution was. This library was created to be used by Puppet's Beaker test framework to enable parallel execution of some of the framework's tasks, and allow people within thier tests to execute code in parallel when wanted.  This solution does not check to see how many processors you have, it just forks as many processes as you ask for.  That means that it will handle a handful of parallel processes well, but could definitely overload your system with ruby processes if you try to spin up a LOT of processes.  If you're looking for something simple and light-weight and on either linux or mac (forking processes is not supported on Windows), then this solution could be what you want.
         | 
| 5 5 |  | 
| 6 6 | 
             
            If you are looking for something to support executing a lot of tasks in parallel as efficiently as possible, you should take a look at the [parallel](https://github.com/grosser/parallel) project.
         | 
| 7 7 |  | 
| 8 8 | 
             
            ## Methods:
         | 
| 9 9 |  | 
| 10 | 
            -
            ### run_in_parallel(&block)
         | 
| 10 | 
            +
            ### run_in_parallel(timeout=nil, kill_all_on_error = false, &block)
         | 
| 11 11 | 
             
            1. You can put whatever methods you want to execute in parallel into a block, and each method will be executed in parallel (unless the method is defined in kernel). 
         | 
| 12 | 
            -
             | 
| 12 | 
            +
                1. Any methods further down the stack won't be affected, only the ones directly within the block.
         | 
| 13 13 | 
             
            2. You can assign the results to instance variables and it just works, no dealing with an array or map of results.
         | 
| 14 14 | 
             
            3. Log STDOUT and STDERR chunked per process to the console so that it is easy to see what happened in which process.
         | 
| 15 15 | 
             
            4. Waits for each process in realtime and logs immediately upon completion of each process
         | 
| 16 | 
            -
            5. If an exception is raised by a child process, it will  | 
| 16 | 
            +
            5. If an exception is raised by a child process, it will optionally (kill_all_on_error) be re-raised in the primary process and kill all other still running child processes. The default will wait for all processes to complete execution before re-raising any unhandled exception from the child processes.
         | 
| 17 | 
            +
            6. Times out by default at 30 minutes. Timeout default can be changed with InParallel::InParallelExecutor.parallel_default_timeout=X, or you can set the timeout param when calling the method
         | 
| 17 18 |  | 
| 18 19 | 
             
            ```ruby
         | 
| 19 20 | 
             
              def method_with_param(name)
         | 
| @@ -55,6 +56,38 @@ hello world | |
| 55 56 | 
             
            hello world, bar
         | 
| 56 57 | 
             
            ```
         | 
| 57 58 |  | 
| 59 | 
            +
            ### Enumerable.each_in_parallel(identifier=nil, timeout=(InParallel::InParallelExecutor.timeout), kill_all_on_error = false, &block)
         | 
| 60 | 
            +
            1. This is very similar to other solutions, except that it directly extends the Enumerable class with an each_in_parallel method, giving you the ability to pretty simply spawn a process for any item in an array or map.
         | 
| 61 | 
            +
            2. Identifies the block location (or caller location if the block does not have a source_location) in the console log to make it clear which block is being executed
         | 
| 62 | 
            +
            3. identifier param is only for logging, otherwise it will use the block source location.
         | 
| 63 | 
            +
            4. If an exception is raised by a child process, it will optionally (kill_all_on_error) be re-raised in the primary process and kill all other still running child processes. The default will wait for all processes to complete execution before re-raising any unhandled exception from the child processes.
         | 
| 64 | 
            +
            5. Times out by default at 30 minutes. Timeout default can be changed with InParallel::InParallelExecutor.parallel_default_timeout=X, or you can set the timeout param when calling the method
         | 
| 65 | 
            +
             | 
| 66 | 
            +
            ```ruby
         | 
| 67 | 
            +
              ["foo", "bar", "baz"].each_in_parallel { |item|
         | 
| 68 | 
            +
                puts item
         | 
| 69 | 
            +
              }
         | 
| 70 | 
            +
             | 
| 71 | 
            +
            ```
         | 
| 72 | 
            +
            STDOUT:
         | 
| 73 | 
            +
            ```
         | 
| 74 | 
            +
            'each_in_parallel' spawned process for '/Users/samwoods/parallel_test/test.rb:77:in `block (2 levels) in <top (required)>'' - PID = '51600'
         | 
| 75 | 
            +
            'each_in_parallel' spawned process for '/Users/samwoods/parallel_test/test.rb:77:in `block (2 levels) in <top (required)>'' - PID = '51601'
         | 
| 76 | 
            +
            'each_in_parallel' spawned process for '/Users/samwoods/parallel_test/test.rb:77:in `block (2 levels) in <top (required)>'' - PID = '51602'
         | 
| 77 | 
            +
             | 
| 78 | 
            +
            ------ Begin output for /Users/samwoods/parallel_test/test.rb:77:in `block (2 levels) in <top (required)>' - 51600
         | 
| 79 | 
            +
            foo
         | 
| 80 | 
            +
            ------ Completed output for /Users/samwoods/parallel_test/test.rb:77:in `block (2 levels) in <top (required)>' - 51600
         | 
| 81 | 
            +
             | 
| 82 | 
            +
            ------ Begin output for /Users/samwoods/parallel_test/test.rb:77:in `block (2 levels) in <top (required)>' - 51601
         | 
| 83 | 
            +
            bar
         | 
| 84 | 
            +
            ------ Completed output for /Users/samwoods/parallel_test/test.rb:77:in `block (2 levels) in <top (required)>' - 51601
         | 
| 85 | 
            +
             | 
| 86 | 
            +
            ------ Begin output for /Users/samwoods/parallel_test/test.rb:77:in `block (2 levels) in <top (required)>' - 51602
         | 
| 87 | 
            +
            baz
         | 
| 88 | 
            +
            ------ Completed output for /Users/samwoods/parallel_test/test.rb:77:in `block (2 levels) in <top (required)>' - 51602
         | 
| 89 | 
            +
            ```
         | 
| 90 | 
            +
             | 
| 58 91 | 
             
            ### run_in_background(ignore_results = true, &block)
         | 
| 59 92 | 
             
            1. This does basically the same thing as run_in_parallel, except it does not wait for execution of all processes to complete, it returns immediately.
         | 
| 60 93 | 
             
            2. You can optionally ignore results completely (default) or delay evaluating the results until later
         | 
| @@ -101,31 +134,7 @@ hello world, bar | |
| 101 134 |  | 
| 102 135 | 
             
            ```
         | 
| 103 136 |  | 
| 104 | 
            -
            ###  | 
| 105 | 
            -
            1.  | 
| 106 | 
            -
            2.  | 
| 107 | 
            -
             | 
| 108 | 
            -
            ```ruby
         | 
| 109 | 
            -
              ["foo", "bar", "baz"].each_in_parallel { |item|
         | 
| 110 | 
            -
                puts |item|
         | 
| 111 | 
            -
              }
         | 
| 112 | 
            -
              
         | 
| 113 | 
            -
            ```
         | 
| 114 | 
            -
            STDOUT:
         | 
| 115 | 
            -
            ```
         | 
| 116 | 
            -
            'each_in_parallel' spawned process for '/Users/samwoods/parallel_test/test.rb:77:in `block (2 levels) in <top (required)>'' - PID = '51600'
         | 
| 117 | 
            -
            'each_in_parallel' spawned process for '/Users/samwoods/parallel_test/test.rb:77:in `block (2 levels) in <top (required)>'' - PID = '51601'
         | 
| 118 | 
            -
            'each_in_parallel' spawned process for '/Users/samwoods/parallel_test/test.rb:77:in `block (2 levels) in <top (required)>'' - PID = '51602'
         | 
| 119 | 
            -
             | 
| 120 | 
            -
            ------ Begin output for /Users/samwoods/parallel_test/test.rb:77:in `block (2 levels) in <top (required)>' - 51600
         | 
| 121 | 
            -
            foo
         | 
| 122 | 
            -
            ------ Completed output for /Users/samwoods/parallel_test/test.rb:77:in `block (2 levels) in <top (required)>' - 51600
         | 
| 123 | 
            -
             | 
| 124 | 
            -
            ------ Begin output for /Users/samwoods/parallel_test/test.rb:77:in `block (2 levels) in <top (required)>' - 51601
         | 
| 125 | 
            -
            bar
         | 
| 126 | 
            -
            ------ Completed output for /Users/samwoods/parallel_test/test.rb:77:in `block (2 levels) in <top (required)>' - 51601
         | 
| 127 | 
            -
             | 
| 128 | 
            -
            ------ Begin output for /Users/samwoods/parallel_test/test.rb:77:in `block (2 levels) in <top (required)>' - 51602
         | 
| 129 | 
            -
            baz
         | 
| 130 | 
            -
            ------ Completed output for /Users/samwoods/parallel_test/test.rb:77:in `block (2 levels) in <top (required)>' - 51602
         | 
| 131 | 
            -
            ```
         | 
| 137 | 
            +
            ### wait_for_processes(timeout=nil, kill_all_on_error = false)
         | 
| 138 | 
            +
            1. Used only after run_in_background with ignore_results=false
         | 
| 139 | 
            +
            2. Optional args for timeout and kill_all_on_error
         | 
| 140 | 
            +
            3. See run_in_background for examples
         | 
    
        data/in_parallel/version.rb
    CHANGED
    
    
    
        data/lib/in_parallel.rb
    CHANGED
    
    | @@ -1,11 +1,17 @@ | |
| 1 | 
            +
            require_relative 'parallel_logger'
         | 
| 1 2 | 
             
            require_relative 'parallel_enumerable'
         | 
| 3 | 
            +
            require 'tempfile'
         | 
| 4 | 
            +
             | 
| 2 5 | 
             
            module InParallel
         | 
| 6 | 
            +
              include ParallelLogger
         | 
| 7 | 
            +
             | 
| 3 8 | 
             
              class InParallelExecutor
         | 
| 4 9 | 
             
                # How many seconds between outputting to stdout that we are waiting for child processes.
         | 
| 5 10 | 
             
                # 0 or < 0 means no signaling.
         | 
| 6 | 
            -
                @@ | 
| 7 | 
            -
                @@ | 
| 8 | 
            -
             | 
| 11 | 
            +
                @@parallel_signal_interval = 30
         | 
| 12 | 
            +
                @@parallel_default_timeout = 1800
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                @@process_infos            = []
         | 
| 9 15 | 
             
                def self.process_infos
         | 
| 10 16 | 
             
                  @@process_infos
         | 
| 11 17 | 
             
                end
         | 
| @@ -21,44 +27,56 @@ module InParallel | |
| 21 27 | 
             
                  @@main_pid
         | 
| 22 28 | 
             
                end
         | 
| 23 29 |  | 
| 24 | 
            -
                def self. | 
| 25 | 
            -
                  @@ | 
| 30 | 
            +
                def self.parallel_default_timeout
         | 
| 31 | 
            +
                  @@parallel_default_timeout
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                def self.parallel_default_timeout=(value)
         | 
| 35 | 
            +
                  @@parallel_default_timeout = value
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                def self.logger
         | 
| 39 | 
            +
                  @@logger
         | 
| 26 40 | 
             
                end
         | 
| 27 41 |  | 
| 28 | 
            -
                def self. | 
| 29 | 
            -
                  @@ | 
| 42 | 
            +
                def self.logger=(value)
         | 
| 43 | 
            +
                  @@logger = value
         | 
| 30 44 | 
             
                end
         | 
| 31 45 |  | 
| 46 | 
            +
                # Runs all methods within the block in parallel and waits for them to complete
         | 
| 47 | 
            +
                #
         | 
| 32 48 | 
             
                # Example - will spawn 2 processes, (1 for each method) wait until they both complete, and log STDOUT:
         | 
| 33 | 
            -
                # | 
| 34 | 
            -
                # | 
| 35 | 
            -
                # | 
| 36 | 
            -
                # | 
| 49 | 
            +
                #   InParallel.run_in_parallel {
         | 
| 50 | 
            +
                #     @result_1 = method1
         | 
| 51 | 
            +
                #     @result_2 = method2
         | 
| 52 | 
            +
                #   }
         | 
| 37 53 | 
             
                # NOTE: Only supports assigning instance variables within the block, not local variables
         | 
| 38 | 
            -
                def self.run_in_parallel(timeout = @@ | 
| 39 | 
            -
                  if  | 
| 54 | 
            +
                def self.run_in_parallel(timeout = @@parallel_default_timeout, kill_all_on_error = false, &block)
         | 
| 55 | 
            +
                  if fork_supported?
         | 
| 40 56 | 
             
                    proxy = BlankBindingParallelProxy.new(block.binding)
         | 
| 41 57 | 
             
                    proxy.instance_eval(&block)
         | 
| 42 58 | 
             
                    return wait_for_processes(proxy, block.binding, timeout, kill_all_on_error)
         | 
| 43 59 | 
             
                  end
         | 
| 44 | 
            -
                   | 
| 60 | 
            +
                  # if fork is not supported
         | 
| 45 61 | 
             
                  block.call
         | 
| 46 62 | 
             
                end
         | 
| 47 63 |  | 
| 64 | 
            +
                # Runs all methods within the block in parallel in the background
         | 
| 65 | 
            +
                #
         | 
| 48 66 | 
             
                # Example - Will spawn a process in the background to run puppet agent on two agents and return immediately:
         | 
| 49 | 
            -
                # | 
| 50 | 
            -
                # | 
| 51 | 
            -
                # | 
| 52 | 
            -
                # | 
| 53 | 
            -
                # | 
| 67 | 
            +
                #   Parallel.run_in_background {
         | 
| 68 | 
            +
                #     @result_1 = method1
         | 
| 69 | 
            +
                #     @result_2 = method2
         | 
| 70 | 
            +
                #   }
         | 
| 71 | 
            +
                #   # Do something else here before waiting for the process to complete
         | 
| 54 72 | 
             
                #
         | 
| 55 | 
            -
                # | 
| 56 | 
            -
                # | 
| 57 | 
            -
                # | 
| 58 | 
            -
                # | 
| 59 | 
            -
                # otherwise @result_1 will evaluate to "unresolved_parallel_result_0"
         | 
| 73 | 
            +
                #   # Optionally wait for the processes to complete before continuing.
         | 
| 74 | 
            +
                #   # Otherwise use run_in_background(true) to clean up the process status and output immediately.
         | 
| 75 | 
            +
                #   wait_for_processes(self)
         | 
| 76 | 
            +
                #
         | 
| 77 | 
            +
                # NOTE: must call get_background_results to allow instance variables in calling object to be set, otherwise @result_1 will evaluate to "unresolved_parallel_result_0"
         | 
| 60 78 | 
             
                def self.run_in_background(ignore_result = true, &block)
         | 
| 61 | 
            -
                  if  | 
| 79 | 
            +
                  if fork_supported?
         | 
| 62 80 | 
             
                    proxy = BlankBindingParallelProxy.new(block.binding)
         | 
| 63 81 | 
             
                    proxy.instance_eval(&block)
         | 
| 64 82 |  | 
| @@ -71,25 +89,22 @@ module InParallel | |
| 71 89 | 
             
                    end
         | 
| 72 90 | 
             
                    return
         | 
| 73 91 | 
             
                  end
         | 
| 74 | 
            -
                   | 
| 92 | 
            +
                  # if fork is not supported
         | 
| 75 93 | 
             
                  result = block.call
         | 
| 76 94 | 
             
                  return nil if ignore_result
         | 
| 77 95 | 
             
                  result
         | 
| 78 96 | 
             
                end
         | 
| 79 97 |  | 
| 80 | 
            -
                # Waits for all processes to complete and logs STDOUT and STDERR in chunks from any processes
         | 
| 81 | 
            -
                # that  | 
| 82 | 
            -
                # @param [Object]  | 
| 83 | 
            -
                # (probably only useful when called by run_in_background)
         | 
| 84 | 
            -
                # @param [Object] binding - The binding of the block to assign return values to instance variables
         | 
| 85 | 
            -
                # (probably only useful when called by run_in_background)
         | 
| 98 | 
            +
                # Waits for all processes to complete and logs STDOUT and STDERR in chunks from any processes that were triggered from this Parallel class
         | 
| 99 | 
            +
                # @param [Object] proxy - The instance of the proxy class that the method was executed within (probably only useful when called by run_in_background)
         | 
| 100 | 
            +
                # @param [Object] binding - The binding of the block to assign return values to instance variables (probably only useful when called by run_in_background)
         | 
| 86 101 | 
             
                # @param [Int] timeout Time in seconds to wait before giving up on a child process
         | 
| 87 | 
            -
                # @param [Boolean] kill_all_on_error Whether to wait for all processes to complete, or fail immediately -
         | 
| 88 | 
            -
                # killing all other forked processes - when one process errors.
         | 
| 102 | 
            +
                # @param [Boolean] kill_all_on_error Whether to wait for all processes to complete, or fail immediately - killing all other forked processes - when one process errors.
         | 
| 89 103 | 
             
                def self.wait_for_processes(proxy = self, binding = nil, timeout = nil, kill_all_on_error = false)
         | 
| 90 104 | 
             
                  raise_error = nil
         | 
| 91 | 
            -
                  timeout ||= @@ | 
| 105 | 
            +
                  timeout ||= @@parallel_default_timeout
         | 
| 92 106 | 
             
                  trap(:INT) do
         | 
| 107 | 
            +
                    # Can't use logger inside of trap
         | 
| 93 108 | 
             
                    puts "Warning, recieved interrupt.  Processing child results and exiting."
         | 
| 94 109 | 
             
                    kill_child_processes
         | 
| 95 110 | 
             
                  end
         | 
| @@ -100,8 +115,8 @@ module InParallel | |
| 100 115 | 
             
                  start_time = Time.now
         | 
| 101 116 | 
             
                  timer = start_time
         | 
| 102 117 | 
             
                  while !@@process_infos.empty? do
         | 
| 103 | 
            -
                    if @@ | 
| 104 | 
            -
                       | 
| 118 | 
            +
                    if @@parallel_signal_interval > 0 && Time.now > timer + @@parallel_signal_interval
         | 
| 119 | 
            +
                      @@logger.debug 'Waiting for child processes.'
         | 
| 105 120 | 
             
                      timer = Time.now
         | 
| 106 121 | 
             
                    end
         | 
| 107 122 | 
             
                    if Time.now > start_time + timeout
         | 
| @@ -116,21 +131,23 @@ module InParallel | |
| 116 131 | 
             
                        # the process completed, get the result and rethrow on error.
         | 
| 117 132 | 
             
                        begin
         | 
| 118 133 | 
             
                          # Print the STDOUT and STDERR for each process with signals for start and end
         | 
| 119 | 
            -
                           | 
| 120 | 
            -
                           | 
| 121 | 
            -
                           | 
| 134 | 
            +
                          @@logger.info "------ Begin output for #{process_info[:method_sym]} - #{process_info[:pid]}"
         | 
| 135 | 
            +
                          # Content from the other thread will already be pre-pended with log stuff (info, warn, date/time, etc)
         | 
| 136 | 
            +
                          # So don't use logger, just use puts.
         | 
| 137 | 
            +
                          puts "  " + File.new(process_info[:std_out], 'r').readlines.join("  ")
         | 
| 138 | 
            +
                          @@logger.info "------ Completed output for #{process_info[:method_sym]} - #{process_info[:pid]}"
         | 
| 122 139 | 
             
                          result = process_info[:result].read
         | 
| 123 140 | 
             
                          marshalled_result = (result.nil? || result.empty?) ? result : Marshal.load(result)
         | 
| 141 | 
            +
                          # Kill all other processes and let them log their stdout before re-raising
         | 
| 142 | 
            +
                          # if a child process raised an error.
         | 
| 124 143 | 
             
                          if marshalled_result.is_a?(Exception)
         | 
| 125 144 | 
             
                            raise_error = marshalled_result.dup
         | 
| 126 145 | 
             
                            kill_child_processes if kill_all_on_error
         | 
| 127 146 | 
             
                            marshalled_result = nil
         | 
| 128 147 | 
             
                          end
         | 
| 129 148 | 
             
                          results_map[process_info[:index]] = {process_info[:tmp_result] => marshalled_result}
         | 
| 130 | 
            -
                          File.delete(process_info[:std_out])
         | 
| 131 | 
            -
                          # Kill all other processes and let them log their stdout before re-raising
         | 
| 132 | 
            -
                          # if a child process raised an error.
         | 
| 133 149 | 
             
                        ensure
         | 
| 150 | 
            +
                          File.delete(process_info[:std_out]) if File.exists?(process_info[:std_out])
         | 
| 134 151 | 
             
                          # close the read end pipe
         | 
| 135 152 | 
             
                          process_info[:result].close unless process_info[:result].closed?
         | 
| 136 153 | 
             
                          @@process_infos.delete(process_info)
         | 
| @@ -164,21 +181,22 @@ module InParallel | |
| 164 181 | 
             
                  ret_val = nil
         | 
| 165 182 | 
             
                  # Communicate the return value of the method or block
         | 
| 166 183 | 
             
                  read_result, write_result = IO.pipe
         | 
| 167 | 
            -
                  Dir.mkdir('tmp') unless Dir.exists? 'tmp'
         | 
| 168 184 | 
             
                  pid = fork do
         | 
| 185 | 
            +
                    Dir.mkdir('tmp') unless Dir.exists? 'tmp'
         | 
| 186 | 
            +
                    stdout_file = File.new("tmp/pp_#{Process.pid}", 'w')
         | 
| 169 187 | 
             
                    exit_status = 0
         | 
| 170 188 | 
             
                    trap(:INT) do
         | 
| 171 | 
            -
                       | 
| 189 | 
            +
                      # Can't use logger inside of trap
         | 
| 190 | 
            +
                      puts "Warning: Interrupt received in child process; exiting #{Process.pid}"
         | 
| 172 191 | 
             
                      kill_child_processes
         | 
| 173 192 | 
             
                      return
         | 
| 174 193 | 
             
                    end
         | 
| 175 | 
            -
                    write_file = File.new("tmp/parallel_process_#{Process.pid}", 'w')
         | 
| 176 194 |  | 
| 177 195 | 
             
                    # IO buffer is 64kb, which isn't much... if debug logging is turned on,
         | 
| 178 196 | 
             
                    # this can be exceeded before a process completes.
         | 
| 179 197 | 
             
                    # Storing output in file rather than using IO.pipe
         | 
| 180 | 
            -
                    STDOUT.reopen( | 
| 181 | 
            -
                    STDERR.reopen( | 
| 198 | 
            +
                    STDOUT.reopen(stdout_file)
         | 
| 199 | 
            +
                    STDERR.reopen(stdout_file)
         | 
| 182 200 |  | 
| 183 201 | 
             
                    begin
         | 
| 184 202 | 
             
                      # close subprocess's copy of read_result since it only needs to write
         | 
| @@ -191,7 +209,7 @@ module InParallel | |
| 191 209 | 
             
                        begin
         | 
| 192 210 | 
             
                          ret_val = ret_val.dup
         | 
| 193 211 | 
             
                        rescue StandardError => err
         | 
| 194 | 
            -
                           | 
| 212 | 
            +
                          @@logger.warn "Warning: return value from child process #{ret_val} " +
         | 
| 195 213 | 
             
                                   "could not be transferred to parent process: #{err.message}"
         | 
| 196 214 | 
             
                        end
         | 
| 197 215 | 
             
                      end
         | 
| @@ -199,11 +217,11 @@ module InParallel | |
| 199 217 | 
             
                      begin
         | 
| 200 218 | 
             
                        Marshal.dump(ret_val, write_result) unless ret_val.nil?
         | 
| 201 219 | 
             
                      rescue StandardError => err
         | 
| 202 | 
            -
                         | 
| 220 | 
            +
                        @@logger.warn "Warning: return value from child process #{ret_val} " +
         | 
| 203 221 | 
             
                                 "could not be transferred to parent process: #{err.message}"
         | 
| 204 222 | 
             
                      end
         | 
| 205 223 | 
             
                    rescue Exception => err
         | 
| 206 | 
            -
                       | 
| 224 | 
            +
                      @@logger.error "Error in process #{pid}: #{err.message}"
         | 
| 207 225 | 
             
                      # Return the error if an error is rescued so we can re-throw in the main process.
         | 
| 208 226 | 
             
                      Marshal.dump(err, write_result)
         | 
| 209 227 | 
             
                      exit_status = 1
         | 
| @@ -212,6 +230,8 @@ module InParallel | |
| 212 230 | 
             
                      exit exit_status
         | 
| 213 231 | 
             
                    end
         | 
| 214 232 | 
             
                  end
         | 
| 233 | 
            +
             | 
| 234 | 
            +
                  @@logger.info "Forked process for #{method_sym} - PID = '#{pid}'"
         | 
| 215 235 | 
             
                  write_result.close
         | 
| 216 236 | 
             
                  # Process.detach returns a thread that will be nil if the process is still running and thr if not.
         | 
| 217 237 | 
             
                  # This allows us to check to see if processes have exited without having to call the blocking Process.wait functions.
         | 
| @@ -220,7 +240,7 @@ module InParallel | |
| 220 240 | 
             
                  process_info = { :wait_thread => wait_thread,
         | 
| 221 241 | 
             
                                   :pid => pid,
         | 
| 222 242 | 
             
                                   :method_sym => method_sym,
         | 
| 223 | 
            -
                                   :std_out => "tmp/ | 
| 243 | 
            +
                                   :std_out => "tmp/pp_#{pid}",
         | 
| 224 244 | 
             
                                   :result => read_result,
         | 
| 225 245 | 
             
                                   :tmp_result => "unresolved_parallel_result_#{@@result_id}",
         | 
| 226 246 | 
             
                                   :index => @@process_infos.count }
         | 
| @@ -229,6 +249,12 @@ module InParallel | |
| 229 249 | 
             
                  process_info
         | 
| 230 250 | 
             
                end
         | 
| 231 251 |  | 
| 252 | 
            +
                def self.fork_supported?
         | 
| 253 | 
            +
                  @@supported ||= Process.respond_to?(:fork)
         | 
| 254 | 
            +
                  @@logger.warn 'Warning: Fork is not supported on this OS, executing block normally' unless @@supported
         | 
| 255 | 
            +
                  @@supported
         | 
| 256 | 
            +
                end
         | 
| 257 | 
            +
             | 
| 232 258 | 
             
                def self.kill_child_processes
         | 
| 233 259 | 
             
                  @@process_infos.each { |process_info|
         | 
| 234 260 | 
             
                    # Send INT to each child process so it returns and can print stdout and stderr to console before exiting.
         | 
| @@ -276,49 +302,65 @@ module InParallel | |
| 276 302 | 
             
                  def method_missing(method_sym, *args, &block)
         | 
| 277 303 | 
             
                    if InParallelExecutor.main_pid == ::Process.pid
         | 
| 278 304 | 
             
                      out = InParallelExecutor._execute_in_parallel("'#{method_sym.to_s}' #{caller_locations[0].to_s}", @object.eval('self')) {send(method_sym, *args, &block)}
         | 
| 279 | 
            -
                      puts "Forked process for '#{method_sym}' - PID = '#{out[:pid]}'\n"
         | 
| 280 305 | 
             
                      out[:tmp_result]
         | 
| 281 306 | 
             
                    end
         | 
| 282 307 | 
             
                  end
         | 
| 283 308 | 
             
                end
         | 
| 284 309 | 
             
              end
         | 
| 285 310 |  | 
| 286 | 
            -
               | 
| 311 | 
            +
              InParallelExecutor.logger = @logger
         | 
| 312 | 
            +
             | 
| 313 | 
            +
              def parallel_signal_interval
         | 
| 314 | 
            +
                InParallelExecutor.parallel_signal_interval
         | 
| 315 | 
            +
              end
         | 
| 316 | 
            +
             | 
| 317 | 
            +
              def parallel_signal_interval=(value)
         | 
| 318 | 
            +
                InParallelExecutor.parallel_signal_interval = value
         | 
| 319 | 
            +
              end
         | 
| 320 | 
            +
             | 
| 321 | 
            +
              def parallel_default_timeout
         | 
| 322 | 
            +
                InParallelExecutor.parallel_default_timeout
         | 
| 323 | 
            +
              end
         | 
| 324 | 
            +
             | 
| 325 | 
            +
              def parallel_default_timeout=(value)
         | 
| 326 | 
            +
                InParallelExecutor.parallel_default_timeout = value
         | 
| 327 | 
            +
              end
         | 
| 328 | 
            +
             | 
| 329 | 
            +
              # Executes each method within a block in a different process.
         | 
| 330 | 
            +
              #
         | 
| 287 331 | 
             
              # Example - Will spawn a process in the background to execute each method
         | 
| 288 | 
            -
              # | 
| 289 | 
            -
              # | 
| 290 | 
            -
              # | 
| 291 | 
            -
              # | 
| 292 | 
            -
              # NOTE - Only instance variables can be assigned the return values of the methods within the block.
         | 
| 293 | 
            -
              # Local variables will not be assigned any values.
         | 
| 332 | 
            +
              #   Parallel.run_in_parallel {
         | 
| 333 | 
            +
              #     @result_1 = method1
         | 
| 334 | 
            +
              #     @result_2 = method2
         | 
| 335 | 
            +
              #   }
         | 
| 336 | 
            +
              # NOTE - Only instance variables can be assigned the return values of the methods within the block. Local variables will not be assigned any values.
         | 
| 294 337 | 
             
              # @param [Int] timeout Time in seconds to wait before giving up on a child process
         | 
| 295 | 
            -
              # @param [Boolean] kill_all_on_error Whether to wait for all processes to complete, or fail immediately -
         | 
| 296 | 
            -
              # killing all other forked processes - when one process errors.
         | 
| 338 | 
            +
              # @param [Boolean] kill_all_on_error Whether to wait for all processes to complete, or fail immediately - killing all other forked processes - when one process errors.
         | 
| 297 339 | 
             
              # @param [Block] block This method will yield to a block of code passed by the caller
         | 
| 298 340 | 
             
              # @return [Array<Result>, Result] the return values of each method within the block
         | 
| 299 341 | 
             
              def run_in_parallel(timeout=nil, kill_all_on_error = false, &block)
         | 
| 300 | 
            -
                timeout ||= InParallelExecutor. | 
| 342 | 
            +
                timeout ||= InParallelExecutor.parallel_default_timeout
         | 
| 301 343 | 
             
                InParallelExecutor.run_in_parallel(timeout, kill_all_on_error, &block)
         | 
| 302 344 | 
             
              end
         | 
| 303 345 |  | 
| 304 | 
            -
              # Forks a process for each method within a block and returns immediately
         | 
| 346 | 
            +
              # Forks a process for each method within a block and returns immediately.
         | 
| 347 | 
            +
              #
         | 
| 305 348 | 
             
              # Example 1 - Will fork a process in the background to execute each method and return immediately:
         | 
| 306 | 
            -
              # | 
| 307 | 
            -
              # | 
| 308 | 
            -
              # | 
| 309 | 
            -
              # | 
| 349 | 
            +
              #   Parallel.run_in_background {
         | 
| 350 | 
            +
              #     @result_1 = method1
         | 
| 351 | 
            +
              #     @result_2 = method2
         | 
| 352 | 
            +
              #   }
         | 
| 310 353 | 
             
              #
         | 
| 311 354 | 
             
              # Example 2 - Will fork a process in the background to execute each method, return immediately, then later
         | 
| 312 355 | 
             
              # wait for the process to complete, printing it's STDOUT and assigning return values to instance variables:
         | 
| 313 | 
            -
              # | 
| 314 | 
            -
              # | 
| 315 | 
            -
              # | 
| 316 | 
            -
              # | 
| 317 | 
            -
              # | 
| 356 | 
            +
              #   Parallel.run_in_background(false) {
         | 
| 357 | 
            +
              #     @result_1 = method1
         | 
| 358 | 
            +
              #     @result_2 = method2
         | 
| 359 | 
            +
              #   }
         | 
| 360 | 
            +
              #   # Do something else here before waiting for the process to complete
         | 
| 318 361 | 
             
              #
         | 
| 319 | 
            -
              # | 
| 320 | 
            -
              # NOTE: must call wait_for_processes to allow instance variables within the block to be set,
         | 
| 321 | 
            -
              # otherwise results will evaluate to "unresolved_parallel_result_X"
         | 
| 362 | 
            +
              #   wait_for_processes
         | 
| 363 | 
            +
              # NOTE: must call wait_for_processes to allow instance variables within the block to be set, otherwise results will evaluate to "unresolved_parallel_result_X"
         | 
| 322 364 | 
             
              # @param [Boolean] ignore_result True if you do not care about the STDOUT or return value of the methods executing in the background
         | 
| 323 365 | 
             
              # @param [Block] block This method will yield to a block of code passed by the caller
         | 
| 324 366 | 
             
              # @return [Array<Result>, Result] the return values of each method within the block
         | 
| @@ -326,14 +368,12 @@ module InParallel | |
| 326 368 | 
             
                InParallelExecutor.run_in_background(ignore_result, &block)
         | 
| 327 369 | 
             
              end
         | 
| 328 370 |  | 
| 329 | 
            -
              # Waits for all processes started by run_in_background to complete execution, then prints STDOUT
         | 
| 330 | 
            -
              # and assigns return values to instance variables.  See :run_in_background
         | 
| 371 | 
            +
              # Waits for all processes started by run_in_background to complete execution, then prints STDOUT and assigns return values to instance variables.  See :run_in_background
         | 
| 331 372 | 
             
              # @param [Int] timeout Time in seconds to wait before giving up on a child process
         | 
| 332 | 
            -
              # @param [Boolean] kill_all_on_error Whether to wait for all processes to complete, or fail immediately -
         | 
| 333 | 
            -
              # killing all other forked processes - when one process errors.
         | 
| 373 | 
            +
              # @param [Boolean] kill_all_on_error Whether to wait for all processes to complete, or fail immediately - killing all other forked processes - when one process errors.
         | 
| 334 374 | 
             
              # @return [Array<Result>, Result] the temporary return values of each method within the block
         | 
| 335 375 | 
             
              def wait_for_processes(timeout=nil, kill_all_on_error = false)
         | 
| 336 | 
            -
                timeout ||= InParallelExecutor. | 
| 376 | 
            +
                timeout ||= InParallelExecutor.parallel_default_timeout
         | 
| 337 377 | 
             
                InParallelExecutor.wait_for_processes(nil, nil, timeout, kill_all_on_error)
         | 
| 338 378 | 
             
              end
         | 
| 339 379 | 
             
            end
         | 
    
        data/lib/parallel_enumerable.rb
    CHANGED
    
    | @@ -1,26 +1,25 @@ | |
| 1 1 | 
             
            # Extending Enumerable to make it easy to do any .each in parallel
         | 
| 2 2 | 
             
            module Enumerable
         | 
| 3 3 | 
             
              # Executes each iteration of the block in parallel
         | 
| 4 | 
            -
              # | 
| 5 | 
            -
              # log STDOUT per process, and return an array of results.
         | 
| 6 | 
            -
              # | 
| 7 | 
            -
              # | 
| 8 | 
            -
              # | 
| 9 | 
            -
              # | 
| 4 | 
            +
              #
         | 
| 5 | 
            +
              # Example - Will execute each iteration in a separate process, in parallel, log STDOUT per process, and return an array of results.
         | 
| 6 | 
            +
              #   my_array = [1,2,3]
         | 
| 7 | 
            +
              #   my_array.each_in_parallel { |int|
         | 
| 8 | 
            +
              #     my_method(int)
         | 
| 9 | 
            +
              #   }
         | 
| 10 10 | 
             
              # @param [String] identifier - Optional identifier for logging purposes only. Will use the block location by default.
         | 
| 11 11 | 
             
              # @param [Int] timeout - Seconds to wait for a forked process to complete before timing out
         | 
| 12 12 | 
             
              # @return [Array<Object>] results - the return value of each block execution.
         | 
| 13 | 
            -
              def each_in_parallel(identifier=nil, timeout=(InParallel::InParallelExecutor. | 
| 14 | 
            -
                if  | 
| 13 | 
            +
              def each_in_parallel(identifier=nil, timeout=(InParallel::InParallelExecutor.parallel_default_timeout), kill_all_on_error = false, &block)
         | 
| 14 | 
            +
                if InParallel::InParallelExecutor.fork_supported? && count > 1
         | 
| 15 15 | 
             
                  identifier ||= "#{caller_locations[0]}"
         | 
| 16 16 | 
             
                  each do |item|
         | 
| 17 17 | 
             
                    out = InParallel::InParallelExecutor._execute_in_parallel(identifier) {block.call(item)}
         | 
| 18 | 
            -
                    puts "'each_in_parallel' forked process for '#{identifier}' - PID = '#{out[:pid]}'\n"
         | 
| 19 18 | 
             
                  end
         | 
| 20 19 | 
             
                  # return the array of values, no need to look up from the map.
         | 
| 21 20 | 
             
                  return InParallel::InParallelExecutor.wait_for_processes(nil, block.binding, timeout, kill_all_on_error)
         | 
| 22 21 | 
             
                end
         | 
| 23 | 
            -
                 | 
| 22 | 
            +
                # If fork is not supported
         | 
| 24 23 | 
             
                block.call
         | 
| 25 24 | 
             
                each(&block)
         | 
| 26 25 | 
             
              end
         | 
| @@ -0,0 +1,13 @@ | |
| 1 | 
            +
            require 'logger'
         | 
| 2 | 
            +
            module InParallel
         | 
| 3 | 
            +
              module ParallelLogger
         | 
| 4 | 
            +
                def self.included(base)
         | 
| 5 | 
            +
                  # Use existing logger if it is defined
         | 
| 6 | 
            +
                  unless(base.instance_variables.include?(:@logger) && base.logger)
         | 
| 7 | 
            +
                    logger = Logger.new(STDOUT)
         | 
| 8 | 
            +
                    logger.send(:extend, self)
         | 
| 9 | 
            +
                    base.instance_variable_set(:@logger, logger)
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: in-parallel
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0.1. | 
| 4 | 
            +
              version: 0.1.10
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - samwoods1
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: exe
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2016-06- | 
| 11 | 
            +
            date: 2016-06-27 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: bundler
         | 
| @@ -66,6 +66,7 @@ files: | |
| 66 66 | 
             
            - in_parallel/version.rb
         | 
| 67 67 | 
             
            - lib/in_parallel.rb
         | 
| 68 68 | 
             
            - lib/parallel_enumerable.rb
         | 
| 69 | 
            +
            - lib/parallel_logger.rb
         | 
| 69 70 | 
             
            homepage: https://github.com/samwoods1/in-parallel
         | 
| 70 71 | 
             
            licenses:
         | 
| 71 72 | 
             
            - MIT
         |