traject 0.16.0 → 0.17.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +1 -0
  3. data/README.md +183 -191
  4. data/bench/bench.rb +1 -1
  5. data/doc/batch_execution.md +14 -0
  6. data/doc/extending.md +14 -12
  7. data/doc/indexing_rules.md +265 -0
  8. data/lib/traject/command_line.rb +12 -41
  9. data/lib/traject/debug_writer.rb +32 -13
  10. data/lib/traject/indexer.rb +101 -24
  11. data/lib/traject/indexer/settings.rb +18 -17
  12. data/lib/traject/json_writer.rb +32 -11
  13. data/lib/traject/line_writer.rb +6 -6
  14. data/lib/traject/macros/basic.rb +1 -1
  15. data/lib/traject/macros/marc21.rb +17 -13
  16. data/lib/traject/macros/marc21_semantics.rb +27 -25
  17. data/lib/traject/macros/marc_format_classifier.rb +39 -25
  18. data/lib/traject/marc4j_reader.rb +36 -22
  19. data/lib/traject/marc_extractor.rb +79 -75
  20. data/lib/traject/marc_reader.rb +33 -25
  21. data/lib/traject/mock_reader.rb +9 -10
  22. data/lib/traject/ndj_reader.rb +7 -7
  23. data/lib/traject/null_writer.rb +1 -1
  24. data/lib/traject/qualified_const_get.rb +12 -2
  25. data/lib/traject/solrj_writer.rb +61 -52
  26. data/lib/traject/thread_pool.rb +45 -45
  27. data/lib/traject/translation_map.rb +59 -27
  28. data/lib/traject/util.rb +3 -3
  29. data/lib/traject/version.rb +1 -1
  30. data/lib/traject/yaml_writer.rb +1 -1
  31. data/test/debug_writer_test.rb +7 -7
  32. data/test/indexer/each_record_test.rb +4 -4
  33. data/test/indexer/macros_marc21_semantics_test.rb +12 -12
  34. data/test/indexer/macros_marc21_test.rb +10 -10
  35. data/test/indexer/macros_test.rb +1 -1
  36. data/test/indexer/map_record_test.rb +6 -6
  37. data/test/indexer/read_write_test.rb +43 -4
  38. data/test/indexer/settings_test.rb +2 -2
  39. data/test/indexer/to_field_test.rb +8 -8
  40. data/test/marc4j_reader_test.rb +4 -4
  41. data/test/marc_extractor_test.rb +33 -25
  42. data/test/marc_format_classifier_test.rb +3 -3
  43. data/test/marc_reader_test.rb +2 -2
  44. data/test/test_helper.rb +3 -3
  45. data/test/test_support/demo_config.rb +52 -48
  46. data/test/translation_map_test.rb +22 -4
  47. data/test/translation_maps/bad_ruby.rb +2 -2
  48. data/test/translation_maps/both_map.rb +1 -1
  49. data/test/translation_maps/default_literal.rb +1 -1
  50. data/test/translation_maps/default_passthrough.rb +1 -1
  51. data/test/translation_maps/ruby_map.rb +1 -1
  52. metadata +7 -31
  53. data/doc/macros.md +0 -103
@@ -1,35 +1,43 @@
1
1
  require 'marc'
2
- require 'traject/ndj_reader'
2
+ require 'traject/ndj_reader'
3
3
 
4
- # A Reader class that can be used with Traject::Indexer.reader, to read
5
- # MARC records.
4
+ # `Traject::MarcReader` uses pure ruby marc gem to parse MARC records. It
5
+ # can read MARC ISO 2709 ('binary'), MARC-XML, and Marc-in-json (newline-delimited-json).
6
6
  #
7
- # Includes Enumerable for convenience.
7
+ # MarcReader can not currently read binary MARC in the MARC8 encoding, see
8
+ # the Traject::Marc4JReader instead.
8
9
  #
9
- # Reads in Marc records using ruby marc. Depends on config variables to
10
- # determine what serialization type to expect, and other parameters controlling
11
- # de-serialization.
10
+ # By default assumes binary MARC encoding, please set marc_source.type setting
11
+ # for XML or json.
12
12
  #
13
- # NOTE: MarcReader can not handle Marc8 encoding. If you need to read binary
14
- # records in MARC8, use Traject::Marc4JReader instead.
13
+ # ## Settings
14
+
15
+ # * "marc_source.type": serialization type. default 'binary'
16
+ # * "binary". standard ISO 2709 "binary" MARC format.
17
+ # * "xml", MarcXML
18
+ # * "json" The "marc-in-json" format, encoded as newline-separated
19
+ # json. (synonym 'ndj'). A simplistic newline-separated json, with no comments
20
+ # allowed, and no unescpaed internal newlines allowed in the json
21
+ # objects -- we just read line by line, and assume each line is a
22
+ # marc-in-json. http://dilettantes.code4lib.org/blog/2010/09/a-proposal-to-serialize-marc-in-json/
23
+ # * "marc_reader.xml_parser": For XML type, which XML parser to tell Marc::Reader
24
+ # to use. Anything recognized by [Marc::Reader :parser
25
+ # argument](http://rdoc.info/github/ruby-marc/ruby-marc/MARC/XMLReader).
26
+ # By default, asks Marc::Reader to take
27
+ # it's best guess as to highest performance available
28
+ # installed option. Probably best to leave as default.
29
+ #
30
+ # ## Example
31
+ #
32
+ # In a configuration file:
15
33
  #
16
- # Settings:
17
- # ["marc_source.type"] serialization type. default 'binary'
18
- # * "binary". Actual marc.
19
- # * "xml", MarcXML
20
- # * "json" The "marc-in-json" format, encoded as newline-separated
21
- # json. A simplistic newline-separated json, with no comments
22
- # allowed, and no unescpaed internal newlines allowed in the json
23
- # objects -- we just read line by line, and assume each line is a
24
- # marc-in-json. http://dilettantes.code4lib.org/blog/2010/09/a-proposal-to-serialize-marc-in-json/
25
- # ["marc_reader.xml_parser"] For XML type, which XML parser to tell Marc::Reader
26
- # to use. Anything recognized by Marc::Reader :parser
27
- # argument. By default, asks Marc::Reader to take
28
- # it's best guess as to highest performance available
29
- # installed option.
34
+ # require 'traject/marc_reader'
30
35
  #
36
+ # settings do
37
+ # provide "reader_class_name", "Traject::MarcReader"
38
+ # provide "marc_source.type", "xml"
39
+ # end
31
40
  #
32
- # Can NOT yet read Marc8, input is always assumed UTF8.
33
41
  class Traject::MarcReader
34
42
  include Enumerable
35
43
 
@@ -64,4 +72,4 @@ class Traject::MarcReader
64
72
  self.internal_reader.each(*args, &block)
65
73
  end
66
74
 
67
- end
75
+ end
@@ -10,15 +10,14 @@ module Traject
10
10
  #
11
11
  # Specify in a config files as follows:
12
12
  #
13
- # require 'traject/mock_writer'
14
- # require 'traject/mock_reader'
15
- #
16
- # settings do
17
- # store "reader_class_name", "Traject::MockReader"
18
- # store "writer_class_name", "Traject::MockWriter"
19
- # store "mock_reader.limit", 4_000 # default is 10_000
20
- # end
21
-
13
+ # require 'traject/mock_writer'
14
+ # require 'traject/mock_reader'
15
+ #
16
+ # settings do
17
+ # store "reader_class_name", "Traject::MockReader"
18
+ # store "writer_class_name", "Traject::MockWriter"
19
+ # store "mock_reader.limit", 4_000 # default is 10_000
20
+ # end
22
21
  class MockReader
23
22
 
24
23
  attr_accessor :limit
@@ -50,7 +49,7 @@ module Traject
50
49
  while true
51
50
  json = this_file_iter.next
52
51
  next unless json =~ /\S/
53
- records << MARC::Record.new_from_hash(JSON.parse(json))
52
+ records << MARC::Record.new_from_hash(JSON.parse(json))
54
53
  end
55
54
  rescue StopIteration
56
55
  end
@@ -8,7 +8,7 @@ require 'zlib'
8
8
 
9
9
  class Traject::NDJReader
10
10
  include Enumerable
11
-
11
+
12
12
  def initialize(input_stream, settings)
13
13
  @settings = settings
14
14
  @input_stream = input_stream
@@ -16,16 +16,16 @@ class Traject::NDJReader
16
16
  @input_stream = Zlib::GzipReader.new(@input_stream, :external_encoding => "UTF-8")
17
17
  end
18
18
  end
19
-
19
+
20
20
  def logger
21
21
  @logger ||= (settings[:logger] || Yell.new(STDERR, :level => "gt.fatal")) # null logger)
22
- end
22
+ end
23
23
 
24
24
  def each
25
25
  unless block_given?
26
26
  return enum_for(:each)
27
27
  end
28
-
28
+
29
29
  @input_stream.each_with_index do |json, i|
30
30
  begin
31
31
  yield MARC::Record.new_from_hash(JSON.parse(json))
@@ -34,7 +34,7 @@ class Traject::NDJReader
34
34
  end
35
35
  end
36
36
  end
37
-
37
+
38
38
  end
39
-
40
-
39
+
40
+
@@ -19,4 +19,4 @@ class Traject::NullWriter
19
19
  # null
20
20
  end
21
21
 
22
- end
22
+ end
@@ -3,7 +3,17 @@
3
3
  #
4
4
  # Method to take a string constant name, including :: qualifications, and
5
5
  # look up the actual constant. Looks up relative to current file.
6
- # REspects leading ::. Etc.
6
+ # Respects leading ::. Etc.
7
+ #
8
+ # class Something
9
+ # include Traject::QualifiedConstGet
10
+ #
11
+ # def foo
12
+ # #...
13
+ # klass = qualified_const_get("Foo::Bar")
14
+ # #...
15
+ # end
16
+ # end
7
17
  module Traject::QualifiedConstGet
8
18
 
9
19
 
@@ -27,4 +37,4 @@ module Traject::QualifiedConstGet
27
37
  path.inject(Object) { |ns,name| ns.const_get(name) }
28
38
  end
29
39
 
30
- end
40
+ end
@@ -1,19 +1,3 @@
1
- # TODO: THREAD POOL
2
- #
3
- # 1) Exception handling in threads, what's the right thing to do
4
- # 2) General count of failed records in a thread safe way, so we can report
5
- # it back from 'close', so process can report it back, and non-zero exit
6
- # code can be emited from command-line.
7
- # 3) back pressure on thread pool. give it a bounded blocking queue instead,
8
- # to make sure thousands of add tasks don't build up, waiting until the end.
9
- # or does that even matter? So what if they build up in the queue and only
10
- # get taken care of at the end, is that okay? I do emit a warning right now
11
- # if it takes more than 60 seconds to process remaining thread pool task queue
12
- # at end.
13
- # 4) No tests yet that actually test thread pool stuff; additionally, may make
14
- # some of the batch tests fail in non-deterministic ways, since batch tests
15
- # assume order of add (and our Mock solr server is not thread safe yet!)
16
-
17
1
  require 'yell'
18
2
 
19
3
  require 'traject'
@@ -26,7 +10,6 @@ require 'thread' # for Mutex
26
10
 
27
11
  #
28
12
  # Writes to a Solr using SolrJ, and the SolrJ HttpSolrServer.
29
- # (sub-class later for the ConcurrentUpdate server?)
30
13
  #
31
14
  # After you call #close, you can check #skipped_record_count if you want
32
15
  # for an integer count of skipped records.
@@ -35,38 +18,64 @@ require 'thread' # for Mutex
35
18
  # you may not get a raise immediately after calling #put, you may get it on
36
19
  # a FUTURE #put or #close. You should get it eventually though.
37
20
  #
38
- # settings:
39
- # [solr.url] Your solr url (required)
40
- # [solrj_writer.server_class_name] Defaults to "HttpSolrServer". You can specify
41
- # another Solr Server sub-class, but it has
42
- # to take a one-arg url constructor. Maybe
43
- # subclass this writer class and overwrite
44
- # instantiate_solr_server! otherwise
45
- # [solrj.jar_dir] Custom directory containing all of the SolrJ jars. All
46
- # jars in this dir will be loaded. Otherwise,
47
- # we load our own packaged solrj jars. This setting
48
- # can't really be used differently in the same app instance,
49
- # since jars are loaded globally.
50
- # [solrj_writer.parser_class_name] A String name of a class in package
51
- # org.apache.solr.client.solrj.impl,
52
- # we'll instantiate one with a zero-arg
53
- # constructor, and pass it as an arg to setParser on
54
- # the SolrServer instance, if present.
55
- # NOTE: For contacting a Solr 1.x server, with the
56
- # recent version of SolrJ used by default, set to
57
- # "XMLResponseParser"
58
- # [solrj_writer.commit_on_close] If true (or string 'true'), send a commit to solr
59
- # at end of #process.
60
- # [solrj_writer.batch_size] If non-nil and more than 1, send documents to
61
- # solr in batches of solrj_writer.batch_size. If nil/1,
62
- # however, an http transaction with solr will be done
63
- # per doc. DEFAULT to 100, which seems to be a sweet spot.
64
- # [solrj_writer.thread_pool] Defaults to 4. A thread pool is used for submitting docs
65
- # to solr. Set to 0 or nil to disable threading. Set to 1,
66
- # there will still be a single bg thread doing the adds.
67
- # May make sense to set higher than number of cores on your
68
- # indexing machine, as these threads will mostly be waiting
69
- # on Solr. Speed/capacity of your solr is more relevant.
21
+ # ## Settings
22
+ #
23
+ # * solr.url: Your solr url (required)
24
+ #
25
+ # * solrj_writer.server_class_name: Defaults to "HttpSolrServer". You can specify
26
+ # another Solr Server sub-class, but it has
27
+ # to take a one-arg url constructor. Maybe
28
+ # subclass this writer class and overwrite
29
+ # instantiate_solr_server! otherwise
30
+ #
31
+ # * solrj.jar_dir: Custom directory containing all of the SolrJ jars. All
32
+ # jars in this dir will be loaded. Otherwise,
33
+ # we load our own packaged solrj jars. This setting
34
+ # can't really be used differently in the same app instance,
35
+ # since jars are loaded globally.
36
+ #
37
+ # * solrj_writer.parser_class_name: A String name of a class in package
38
+ # org.apache.solr.client.solrj.impl,
39
+ # we'll instantiate one with a zero-arg
40
+ # constructor, and pass it as an arg to setParser on
41
+ # the SolrServer instance, if present.
42
+ # NOTE: For contacting a Solr 1.x server, with the
43
+ # recent version of SolrJ used by default, set to
44
+ # "XMLResponseParser"
45
+ #
46
+ # * solrj_writer.commit_on_close: If true (or string 'true'), send a commit to solr
47
+ # at end of #process.
48
+ #
49
+ # * solrj_writer.batch_size: If non-nil and more than 1, send documents to
50
+ # solr in batches of solrj_writer.batch_size. If nil/1,
51
+ # however, an http transaction with solr will be done
52
+ # per doc. DEFAULT to 100, which seems to be a sweet spot.
53
+ #
54
+ # * solrj_writer.thread_pool: Defaults to 1. A thread pool is used for submitting docs
55
+ # to solr. Set to 0 or nil to disable threading. Set to 1,
56
+ # there will still be a single bg thread doing the adds. For
57
+ # very fast Solr servers and very fast indexing processes, may
58
+ # make sense to increase this value to throw at Solr as fast as it
59
+ # can catch.
60
+ #
61
+ # ## Example
62
+ #
63
+ # settings do
64
+ # provide "writer_class_name", "Traject::SolrJWriter"
65
+ #
66
+ # # This is just regular ruby, so don't be afraid to have conditionals!
67
+ # # Switch on hostname, for test and production server differences
68
+ # if Socket.gethostname =~ /devhost/
69
+ # provide "solr.url", "http://my.dev.machine:9033/catalog"
70
+ # else
71
+ # provide "solr.url", "http://my.production.machine:9033/catalog"
72
+ # end
73
+ #
74
+ # provide "solrj_writer.parser_class_name", "BinaryResponseParser" # for Solr 4.x
75
+ # # provide "solrj_writer.parser_class_name", "XMLResponseParser" # For solr 1.x or 3.x
76
+ #
77
+ # provide "solrj_writer.commit_on_close", "true"
78
+ # end
70
79
  class Traject::SolrJWriter
71
80
  # just a tuple of a SolrInputDocument
72
81
  # and a Traject::Indexer::Context it came from
@@ -150,7 +159,7 @@ class Traject::SolrJWriter
150
159
 
151
160
  if settings["solrj_writer.batch_size"].to_i > 1
152
161
  ready_batch = []
153
-
162
+
154
163
  batched_queue.add(package)
155
164
  if batched_queue.size >= settings["solrj_writer.batch_size"].to_i
156
165
  batched_queue.drain_to(ready_batch)
@@ -164,7 +173,7 @@ class Traject::SolrJWriter
164
173
  end
165
174
  end
166
175
 
167
- @thread_pool.maybe_in_thread_pool { batch_add_document_packages(ready_batch) }
176
+ @thread_pool.maybe_in_thread_pool { batch_add_document_packages(ready_batch) }
168
177
  end
169
178
  else # non-batched add, add one at a time.
170
179
  @thread_pool.maybe_in_thread_pool { add_one_document_package(package) }
@@ -192,7 +201,7 @@ class Traject::SolrJWriter
192
201
  # shared state batched_queue in a mutex.
193
202
  def batch_add_document_packages(current_batch)
194
203
  begin
195
- a = current_batch.collect {|package| package.solr_document }
204
+ a = current_batch.collect {|package| package.solr_document }
196
205
  solr_server.add( a )
197
206
 
198
207
  $stderr.write "%" if @debug_ascii_progress
@@ -1,47 +1,47 @@
1
1
  module Traject
2
2
  # An abstraction wrapping a threadpool executor in some configuration choices
3
- # and other apparatus.
3
+ # and other apparatus.
4
+ #
5
+ # 1) Initialize with chosen pool size -- we create fixed size pools, where
6
+ # core and max sizes are the same.
4
7
  #
5
- # 1) Initialize with chosen pool size -- we create fixed size pools, where
6
- # core and max sizes are the same.
7
-
8
8
  # 2) If initialized with nil for threadcount, no thread pool will actually
9
- # be created, and all threadpool-related methods become no-ops. We call this
10
- # the nil/null threadpool. A non-nil threadpool requires jruby, but you can
11
- # create a null Traject::ThreadPool.new(nil) under MRI without anything
12
- # complaining.
9
+ # be created, and all threadpool-related methods become no-ops. We call this
10
+ # the nil/null threadpool. A non-nil threadpool requires jruby, but you can
11
+ # create a null Traject::ThreadPool.new(nil) under MRI without anything
12
+ # complaining.
13
13
  #
14
14
  # 3) Use the #maybe_in_threadpool method to send blocks to thread pool for
15
- # execution -- if no threadpool configured your block will just be
16
- # executed in calling thread. Be careful to not refer to any non-local
17
- # variables in the block, unless the variable has an object you can
18
- # use thread-safely!
15
+ # execution -- if no threadpool configured your block will just be
16
+ # executed in calling thread. Be careful to not refer to any non-local
17
+ # variables in the block, unless the variable has an object you can
18
+ # use thread-safely!
19
19
  #
20
20
  # 4) Thread pools are java.util.concurrent.ThreadPoolExecutor, manually created
21
- # with a work queue that will buffer up to (pool_size*3) tasks. If queue is full,
22
- # the ThreadPoolExecutor is set up to use the ThreadPoolExecutor.CallerRunsPolicy,
23
- # meaning the block will end up executing in caller's own thread. With the kind
24
- # of work we're doing, where each unit of work is small and there are many of them--
25
- # the CallerRunsPolicy serves as an effective 'back pressure' mechanism to keep
26
- # the work queue from getting too large and exhausting memory, when producers are
27
- # faster than consumers.
21
+ # with a work queue that will buffer up to (pool_size*3) tasks. If queue is full,
22
+ # the ThreadPoolExecutor is set up to use the ThreadPoolExecutor.CallerRunsPolicy,
23
+ # meaning the block will end up executing in caller's own thread. With the kind
24
+ # of work we're doing, where each unit of work is small and there are many of them--
25
+ # the CallerRunsPolicy serves as an effective 'back pressure' mechanism to keep
26
+ # the work queue from getting too large and exhausting memory, when producers are
27
+ # faster than consumers.
28
28
  #
29
- # 5) Any exceptions raised by pool-executed work are captured accumulated in a thread-safe
30
- # manner, and can be re-raised in the thread of your choice by calling
31
- # #raise_collected_exception!
29
+ # 5) Any exceptions raised by pool-executed work are captured accumulated in a thread-safe
30
+ # manner, and can be re-raised in the thread of your choice by calling
31
+ # #raise_collected_exception!
32
32
  #
33
- # 6) When you are done with the threadpool, you can and must call
34
- # #shutdown_and_wait, which will wait for all current queued work
35
- # to complete, then return. You can not give any more work to the pool
36
- # after you do this. By default it'll wait pretty much forever, which should
37
- # be fine. If you never call shutdown, the pool will keep running forever
38
- # and not allow your program to exit!
33
+ # 6) When you are done with the threadpool, you can and must call
34
+ # #shutdown_and_wait, which will wait for all current queued work
35
+ # to complete, then return. You can not give any more work to the pool
36
+ # after you do this. By default it'll wait pretty much forever, which should
37
+ # be fine. If you never call shutdown, the pool will keep running forever
38
+ # and not allow your program to exit!
39
39
  #
40
- # 7) We will keep track of total times a block is run in thread pool, and
41
- # total elapsed (wall) time of running all blocks, so an average_execution_ms
42
- # time can be given. #average_execution_ms may be inaccurate if called when
43
- # threads are still executing, as it's not entirely thread safe (may get
44
- # an off by one as to total iterations)
40
+ # 7) We will keep track of total times a block is run in thread pool, and
41
+ # total elapsed (wall) time of running all blocks, so an average_execution_ms
42
+ # time can be given. #average_execution_ms may be inaccurate if called when
43
+ # threads are still executing, as it's not entirely thread safe (may get
44
+ # an off by one as to total iterations)
45
45
  class ThreadPool
46
46
  attr_reader :pool_size, :label, :queue_capacity
47
47
 
@@ -60,15 +60,15 @@ module Traject
60
60
  rejectedExecutionHandler = java.util.concurrent.ThreadPoolExecutor::CallerRunsPolicy.new
61
61
 
62
62
  # keepalive times don't matter, we are setting core and max pool to
63
- # same thing, fixed size pool.
63
+ # same thing, fixed size pool.
64
64
  @thread_pool = java.util.concurrent.ThreadPoolExecutor.new(
65
- @pool_size, @pool_size, 0, java.util.concurrent.TimeUnit::MILLISECONDS,
65
+ @pool_size, @pool_size, 0, java.util.concurrent.TimeUnit::MILLISECONDS,
66
66
  blockingQueue, rejectedExecutionHandler)
67
67
 
68
- # A thread-safe queue to collect exceptions cross-threads.
68
+ # A thread-safe queue to collect exceptions cross-threads.
69
69
  # We make it small, we really only need to store the first
70
70
  # exception, we don't care too much about others. But we'll
71
- # keep the first 20, why not.
71
+ # keep the first 20, why not.
72
72
  @async_exception_queue = java.util.concurrent.ArrayBlockingQueue.new(20)
73
73
  end
74
74
  end
@@ -101,7 +101,7 @@ module Traject
101
101
  # # and would be pointing to a different string now!
102
102
  #
103
103
  # Note, that just makes block-local variables, it doesn't
104
- # help you with whether a data structure itself is thread safe.
104
+ # help you with whether a data structure itself is thread safe.
105
105
  def maybe_in_thread_pool(*args)
106
106
  start_t = Time.now
107
107
 
@@ -121,7 +121,7 @@ module Traject
121
121
 
122
122
  # Just for monitoring/debugging purposes, we'll return the work queue
123
123
  # used by the threadpool. Don't recommend you do anything with it, as
124
- # the original java.util.concurrent docs make the same recommendation.
124
+ # the original java.util.concurrent docs make the same recommendation.
125
125
  def queue
126
126
  @thread_pool && @thread_pool.queue
127
127
  end
@@ -129,20 +129,20 @@ module Traject
129
129
  # thread-safe way of storing an exception, to raise
130
130
  # later in a different thread. We don't guarantee
131
131
  # that we can store more than one at a time, only
132
- # the first one recorded may be stored.
132
+ # the first one recorded may be stored.
133
133
  def collect_exception(e)
134
134
  # offer will silently do nothing if the queue is full, that's fine
135
- # with us.
135
+ # with us.
136
136
  @async_exception_queue.offer(e)
137
137
  end
138
138
 
139
139
  # If there's a stored collected exception, raise it
140
140
  # again now. Call this to re-raise exceptions caught in
141
- # other threads in the thread of your choice.
141
+ # other threads in the thread of your choice.
142
142
  #
143
143
  # If you call this method on a ThreadPool initialized with nil
144
144
  # as a non-functioning threadpool -- then this method is just
145
- # a no-op.
145
+ # a no-op.
146
146
  def raise_collected_exception!
147
147
  if @async_exception_queue && e = @async_exception_queue.poll
148
148
  raise e
@@ -151,7 +151,7 @@ module Traject
151
151
 
152
152
  # shutdown threadpool, and wait for all work to complete.
153
153
  # this one is also a no-op if you have a null ThreadPool that
154
- # doesn't really have a threadpool at all.
154
+ # doesn't really have a threadpool at all.
155
155
  #
156
156
  # returns elapsed time in seconds it took to shutdown
157
157
  def shutdown_and_wait
@@ -168,4 +168,4 @@ module Traject
168
168
  end
169
169
 
170
170
  end
171
- end
171
+ end