Repository: kpcyrd/sn0int (language: rust)

Issue:
Multithreading
It should be possible the run scripts with concurrency. This has some implications:

- the database either needs locking or the code should ensure only one operation runs at a time
- we need to change the spinner, either stack them or show multiple targets in the label
- the log lines would need to have more context, often it's not clear which source item produced a certain log line


Discussion / hints:

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index 95ad6cb2..b1087f6a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -38,7 +38,7 @@ name = "arrayvec"
 version = "0.4.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
+ "nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -300,7 +300,7 @@ version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "crossbeam-epoch 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "crossbeam-utils 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "crossbeam-utils 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "parking_lot 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "smallvec 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -321,7 +321,7 @@ version = "0.6.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "crossbeam-epoch 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "crossbeam-utils 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "crossbeam-utils 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -334,7 +334,7 @@ dependencies = [
  "crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
+ "nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
  "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -345,7 +345,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "crossbeam-utils 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "crossbeam-utils 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -366,7 +366,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "crossbeam-utils"
-version = "0.6.0"
+version = "0.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -391,7 +391,7 @@ dependencies = [
  "itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "phf 0.7.23 (registry+https://github.com/rust-lang/crates.io-index)",
- "proc-macro2 0.4.21 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2 0.4.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "procedural-masquerade 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
  "smallvec 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -404,10 +404,10 @@ version = "0.3.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "phf_codegen 0.7.23 (registry+https://github.com/rust-lang/crates.io-index)",
- "proc-macro2 0.4.21 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2 0.4.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "procedural-masquerade 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 0.15.19 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.15.20 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -547,7 +547,7 @@ dependencies = [
 
 [[package]]
 name = "env_logger"
-version = "0.5.13"
+version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -606,15 +606,15 @@ name = "failure_derive"
 version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "proc-macro2 0.4.21 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2 0.4.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 0.15.19 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.15.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "filetime"
-version = "0.2.2"
+version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -773,9 +773,9 @@ dependencies = [
  "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "mac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "markup5ever 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "proc-macro2 0.4.21 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2 0.4.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 0.15.19 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.15.20 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -1238,7 +1238,7 @@ dependencies = [
 
 [[package]]
 name = "nodrop"
-version = "0.1.12"
+version = "0.1.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
@@ -1445,7 +1445,7 @@ dependencies = [
 
 [[package]]
 name = "proc-macro2"
-version = "0.4.21"
+version = "0.4.23"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1491,7 +1491,7 @@ name = "quote"
 version = "0.6.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "proc-macro2 0.4.21 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2 0.4.23 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -1536,6 +1536,33 @@ dependencies = [
  "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "rand"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_chacha 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_isaac 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_pcg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_xorshift 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "rand_core"
 version = "0.2.2"
@@ -1549,6 +1576,39 @@ name = "rand_core"
 version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "rand_hc"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_isaac"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_pcg"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_xorshift"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "rayon"
 version = "0.7.1"
@@ -1642,7 +1702,7 @@ dependencies = [
 
 [[package]]
 name = "reqwest"
-version = "0.9.4"
+version = "0.9.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1935,9 +1995,9 @@ name = "serde_derive"
 version = "1.0.80"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "proc-macro2 0.4.21 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2 0.4.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 0.15.19 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.15.20 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -1966,7 +2026,7 @@ name = "servo_arc"
 version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
+ "nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
  "stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -2022,7 +2082,7 @@ dependencies = [
  "diesel 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "diesel_migrations 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "dirs 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)",
+ "env_logger 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "hlua-badtouch 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "kuchiki 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2035,7 +2095,7 @@ dependencies = [
  "opener 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "pledge 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "publicsuffix 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "regex 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "rustyline 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "separator 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2048,7 +2108,8 @@ dependencies = [
  "sn0int-common 0.3.0",
  "structopt 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
  "syscallz 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "tar 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tar 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)",
+ "threadpool 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "toml 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "trust-dns-proto 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "unveil 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2080,7 +2141,7 @@ dependencies = [
  "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "maplit 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "oauth2 2.0.0-alpha.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "reqwest 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "reqwest 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "rocket 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
  "rocket_codegen 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
  "rocket_contrib 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2139,7 +2200,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "phf_generator 0.7.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "phf_shared 0.7.23 (registry+https://github.com/rust-lang/crates.io-index)",
- "proc-macro2 0.4.21 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2 0.4.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
  "string_cache_shared 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
@@ -2169,9 +2230,9 @@ version = "0.2.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "heck 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "proc-macro2 0.4.21 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2 0.4.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 0.15.19 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.15.20 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -2204,17 +2265,17 @@ name = "syn"
 version = "0.14.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "proc-macro2 0.4.21 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2 0.4.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
  "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "syn"
-version = "0.15.19"
+version = "0.15.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "proc-macro2 0.4.21 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2 0.4.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
  "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
@@ -2232,9 +2293,9 @@ name = "synstructure"
 version = "0.10.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "proc-macro2 0.4.21 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2 0.4.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 0.15.19 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.15.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -2250,10 +2311,10 @@ dependencies = [
 
 [[package]]
 name = "tar"
-version = "0.4.19"
+version = "0.4.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "filetime 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "filetime 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
  "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
  "xattr 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2321,6 +2382,14 @@ dependencies = [
  "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "threadpool"
+version = "1.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "time"
 version = "0.1.40"
@@ -2872,7 +2941,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum crossbeam-epoch 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2449aaa4ec7ef96e5fb24db16024b935df718e9ae1cec0a1e68feeca2efca7b8"
 "checksum crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2760899e32a1d58d5abb31129f8fae5de75220bc2176e77ff7c627ae45c918d9"
 "checksum crossbeam-utils 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "677d453a17e8bd2b913fa38e8b9cf04bcdbb5be790aa294f2389661d72036015"
-"checksum crossbeam-utils 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6efda20eef4ccf2a862d442a1e0319d568f9133e16a085a1f8126fe9b6da852d"
+"checksum crossbeam-utils 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c55913cc2799171a550e307918c0a360e8c16004820291bf3b638969b4a01816"
 "checksum crypto-mac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5"
 "checksum cssparser 0.24.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b200a7193703a615c8d2751fed1ede39b9c4b3905e09d1ec7064a24688c190fc"
 "checksum cssparser-macros 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "b16e382d9b983fdb9ac6a36b37fdeb84ce3ea81f749febfee3463cfa7f24275e"
@@ -2890,14 +2959,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum dtoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6d301140eb411af13d3115f9a562c85cc6b541ade9dfa314132244aaee7489dd"
 "checksum dtoa-short 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "59020b8513b76630c49d918c33db9f4c91638e7d3404a28084083b87e33f76f2"
 "checksum encoding_rs 0.8.10 (registry+https://github.com/rust-lang/crates.io-index)" = "065f4d0c826fdaef059ac45487169d918558e3cf86c9d89f6e81cf52369126e5"
-"checksum env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)" = "15b0a4d2e39f8420210be8b27eeda28029729e2fd4291019455016c348240c38"
+"checksum env_logger 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "afb070faf94c85d17d50ca44f6ad076bce18ae92f0037d350947240a36e9d42e"
 "checksum errno 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c2a071601ed01b988f896ab14b95e67335d1eeb50190932a1320f7fe3cadc84e"
 "checksum errno-dragonfly 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "14ca354e36190500e1e1fb267c647932382b54053c50b14970856c0b00a35067"
 "checksum error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "07e791d3be96241c77c43846b665ef1384606da2cd2a48730abe606a12906e02"
 "checksum error-chain 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6930e04918388a9a2e41d518c25cf679ccafe26733fb4127dbf21993f2575d46"
 "checksum failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6dd377bcc1b1b7ce911967e3ec24fa19c3224394ec05b54aa7b083d498341ac7"
 "checksum failure_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "64c2d913fe8ed3b6c6518eedf4538255b989945c14c2a7d5cbff62a5e2120596"
-"checksum filetime 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6f57e64bf5eae62efd4efed005ae81a7115012a61f522bba54542e1a556af921"
+"checksum filetime 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b3ea0c97183a611b1673e5e28b160d7e1035106cad053c988aae3bbd996fdcce"
 "checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
 "checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
 "checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
@@ -2965,7 +3034,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88"
 "checksum new_debug_unreachable 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0cdc457076c78ab54d5e0d6fa7c47981757f1e34dc39ff92787f217dede586c4"
 "checksum nix 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d37e713a259ff641624b6cb20e3b12b2952313ba36b6823c0f16e6cfd9e5de17"
-"checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2"
+"checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945"
 "checksum nom 4.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9c349f68f25f596b9f44cf0e7c69752a5c633b0550c3ff849518bfba0233774a"
 "checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30"
 "checksum oauth2 2.0.0-alpha.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0b03cbda9babb980e485e84c7230812f99568b23c68b04008dd0b22f4179a57a"
@@ -2991,7 +3060,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum pq-sys 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "6ac25eee5a0582f45a67e837e350d784e7003bd29a5f460796772061ca49ffda"
 "checksum precomputed-hash 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
 "checksum proc-macro2 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1b06e2f335f48d24442b35a19df506a835fb3547bc3c06ef27340da9acf5cae7"
-"checksum proc-macro2 0.4.21 (registry+https://github.com/rust-lang/crates.io-index)" = "ab2fc21ba78ac73e4ff6b3818ece00be4e175ffbef4d0a717d978b48b24150c4"
+"checksum proc-macro2 0.4.23 (registry+https://github.com/rust-lang/crates.io-index)" = "88dae56b29da695d783ea7fc5a90de281f79eb38407e77f6d674dd8befc4ac47"
 "checksum procedural-masquerade 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9a1574a51c3fd37b26d2c0032b649d08a7d51d4cca9c41bbc5bf7118fa4509d0"
 "checksum publicsuffix 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5afecba86dcf1e4fd610246f89899d1924fe12e1e89f555eb7c7f710f3c5ad1d"
 "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0"
@@ -3002,8 +3071,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "15a732abf9d20f0ad8eeb6f909bf6868722d9a06e1e50802b6a70351f40b4eb1"
 "checksum rand 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8356f47b32624fef5b3301c1be97e5944ecdd595409cc5da11d05f211db6cfbd"
 "checksum rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e464cd887e869cddcae8792a4ee31d23c7edd516700695608f5b98c67ee0131c"
+"checksum rand 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de3f08319b5395bd19b70e73c4c465329495db02dafeb8ca711a20f1c2bd058c"
+"checksum rand_chacha 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "771b009e3a508cb67e8823dda454aaa5368c7bc1c16829fb77d3e980440dd34a"
 "checksum rand_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1961a422c4d189dfb50ffa9320bf1f2a9bd54ecb92792fb9477f99a1045f3372"
 "checksum rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0905b6b7079ec73b314d4c748701f6931eb79fd97c668caa3f1899b22b32c6db"
+"checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4"
+"checksum rand_isaac 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2d6ecfe9ebf36acd47a49d150990b047a5f7db0a7236ee2414b7ff5cc1097c7b"
+"checksum rand_pcg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "086bd09a33c7044e56bb44d5bdde5a60e7f119a9e95b0775f545de759a32fe05"
+"checksum rand_xorshift 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "effa3fcaa47e18db002bdde6060944b6d2f9cfd8db471c30e873448ad9187be3"
 "checksum rayon 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a77c51c07654ddd93f6cb543c7a849863b03abc7e82591afda6dc8ad4ac3ac4a"
 "checksum rayon-core 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b055d1e92aba6877574d8fe604a63c8b5df60f60e5982bf7ccbb1338ea527356"
 "checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1"
@@ -3014,7 +3089,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum regex-syntax 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7d707a4fa2637f2dca2ef9fd02225ec7661fe01a53623c1e6515b6916511f7a7"
 "checksum regex-syntax 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "fbc557aac2b708fe84121caf261346cc2eed71978024337e42eb46b8a252ac6e"
 "checksum remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5"
-"checksum reqwest 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)" = "00a5870d8edc74fc6e1eb58edbd2815d2243e1a2255d6bf9c82a7a875901b5db"
+"checksum reqwest 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ab52e462d1e15891441aeefadff68bdea005174328ce3da0a314f2ad313ec837"
 "checksum resolv-conf 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c62bd95a41841efdf7fca2ae9951e64a8d8eae7e5da196d8ce489a2241491a92"
 "checksum ring 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1f2a6dc7fc06a05e6de183c5b97058582e9da2de0c136eafe49609769c507724"
 "checksum ring 0.13.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7ed733c36010c3d4d4718588f16a6c06a670b01c0047029ae81c3ca0acd81ff5"
@@ -3067,11 +3142,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad"
 "checksum syn 0.13.11 (registry+https://github.com/rust-lang/crates.io-index)" = "14f9bf6292f3a61d2c716723fdb789a41bbe104168e6f496dc6497e531ea1b9b"
 "checksum syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)" = "261ae9ecaa397c42b960649561949d69311f08eeaea86a65696e6e46517cf741"
-"checksum syn 0.15.19 (registry+https://github.com/rust-lang/crates.io-index)" = "39054bb43f2c5e4f3aef47718a391bf397c1b820fefc86f467d4d354f67bf7ef"
+"checksum syn 0.15.20 (registry+https://github.com/rust-lang/crates.io-index)" = "8886c8d2774e853fcd7d9d2131f6e40ba46c9c0e358e4d57178452abd6859bb0"
 "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6"
 "checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015"
 "checksum syscallz 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e7ba6c26a802c1b51eb4a9c88a37000c6b7e443994d6b53dfcdf85b69dbec045"
-"checksum tar 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)" = "69e16840a1e0a1f1a880b739ef1cc6a4b85496c99b8aa786ccffce6e0c15624c"
+"checksum tar 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)" = "a303ba60a099fcd2aaa646b14d2724591a96a75283e4b7ed3d1a1658909d9ae2"
 "checksum tempfile 3.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "55c1195ef8513f3273d55ff59fe5da6940287a0d7a98331254397f464833675b"
 "checksum tendril 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9de21546595a0873061940d994bbbc5c35f024ae4fd61ec5c5b159115684f508"
 "checksum termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4096add70612622289f2fdcdbd5086dc81c1e2675e6ae58d6c4f62a16c6d7f2f"
@@ -3079,6 +3154,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6"
 "checksum thin-slice 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c"
 "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
+"checksum threadpool 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e2f0c90a5f3459330ac8bc0d2f879c693bb7a2f59689c1083fc4ef83834da865"
 "checksum time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "d825be0eb33fda1a7e68012d51e9c7f451dc1a69391e7fdc197060bb8c56667b"
 "checksum tokio 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "6e93c78d23cc61aa245a8acd2c4a79c4d7fa7fb5c3ca90d5737029f043a84895"
 "checksum tokio-codec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5c501eceaf96f0e1793cf26beb63da3d11c738c4a943fdf3746d81d64684c39f"
diff --git a/Cargo.toml b/Cargo.toml
index dcd4796a..c271c766 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -19,11 +19,11 @@ members = ["sn0int-registry/sn0int-common",
 sn0int-common = { version="0.3.0", path="sn0int-registry/sn0int-common" }
 rustyline = "2"
 log = "0.4"
-env_logger = "0.5"
+env_logger = "0.6"
 hlua-badtouch = "0.4"
 structopt = "0.2"
 failure = "0.1"
-rand = "0.5"
+rand = "0.6"
 colored = "1.6"
 lazy_static = "1.0"
 shellwords = "1.0"
@@ -51,6 +51,7 @@ toml = "0.4"
 maxminddb = "0.10.0"
 tar = "0.4.17"
 libflate = "0.1.14"
+threadpool = "1.7"
 
 [target.'cfg(target_os="linux")'.dependencies]
 caps = "0.3"
diff --git a/examples/stacked-spinners.rs b/examples/stacked-spinners.rs
new file mode 100644
index 00000000..6ab4db5d
--- /dev/null
+++ b/examples/stacked-spinners.rs
@@ -0,0 +1,25 @@
+extern crate sn0int;
+
+use sn0int::term::StackedSpinners;
+use std::thread;
+use std::time::Duration;
+
+fn main() {
+    let mut stack = StackedSpinners::new();
+    stack.add("1".into(), String::from("spinner1"));
+    stack.add("2".into(), String::from("spinner2"));
+    stack.add("3".into(), String::from("spinner3"));
+
+    for x in 1..=3 {
+        for _ in 0..50 {
+            thread::sleep(Duration::from_millis(100));
+            stack.tick();
+        }
+        // stack.log("ohai");
+        stack.remove(&x.to_string());
+    }
+
+    stack.clear();
+
+    // stack.finish("Done".to_string());
+}
diff --git a/src/args.rs b/src/args.rs
index a20cac9c..68b052f4 100644
--- a/src/args.rs
+++ b/src/args.rs
@@ -53,6 +53,8 @@ pub struct Run {
     pub module: Option<String>,
     #[structopt(short="f", long="file", conflicts_with="module")]
     pub file: Option<String>,
+    #[structopt(short="j", long="threads", default_value="1")]
+    pub threads: usize,
 }
 
 #[derive(Debug, StructOpt)]
diff --git a/src/cmd/run_cmd.rs b/src/cmd/run_cmd.rs
index 54389444..0b67cc95 100644
--- a/src/cmd/run_cmd.rs
+++ b/src/cmd/run_cmd.rs
@@ -13,6 +13,8 @@ use worker;
 
 #[derive(Debug, StructOpt)]
 pub struct Args {
+    #[structopt(short="j", long="threads", default_value="1")]
+    threads: usize,
 }
 
 fn prepare_arg<T: Serialize + Model>(x: T) -> Result<(serde_json::Value, Option<String>)> {
@@ -28,7 +30,7 @@ fn prepare_args<T: Scopable + Serialize + Model>(db: &Database, filter: &Filter)
         .collect()
 }
 
-pub fn execute(rl: &mut Readline) -> Result<()> {
+pub fn execute(rl: &mut Readline, threads: usize) -> Result<()> {
     let module = rl.module()
         .map(|m| m.to_owned())
         .ok_or_else(|| format_err!("No module selected"))?;
@@ -42,22 +44,18 @@ pub fn execute(rl: &mut Readline) -> Result<()> {
         Some(Source::Urls) => prepare_args::<Url>(rl.db(), &filter),
         Some(Source::Emails) => prepare_args::<Email>(rl.db(), &filter),
         None => Ok(vec![(serde_json::Value::Null, None)]),
-    };
-
-    rl.catch_ctrl();
-    for (arg, pretty_arg) in args? {
-        worker::spawn(rl, module.clone(), arg, &pretty_arg);
-        if rl.ctrlc_received() {
-            break;
-        }
-    }
-    rl.reset_ctrlc();
+    }?;
+
+    rl.signal_register().catch_ctrl();
+    worker::spawn(rl, &module, args, threads);
+    rl.signal_register().reset_ctrlc();
+
     term::info(&format!("Finished {}", module.canonical()));
 
     Ok(())
 }
 
 pub fn run(rl: &mut Readline, args: &[String]) -> Result<()> {
-    let _args = Args::from_iter_safe(args)?;
-    execute(rl)
+    let args = Args::from_iter_safe(args)?;
+    execute(rl, args.threads)
 }
diff --git a/src/engine/ctx.rs b/src/engine/ctx.rs
index 155a40cc..99c4d49e 100644
--- a/src/engine/ctx.rs
+++ b/src/engine/ctx.rs
@@ -13,7 +13,7 @@ use std::result;
 use std::sync::{Arc, Mutex};
 use chrootable_https::dns::DnsConfig;
 use web::{HttpSession, HttpRequest, RequestOptions};
-use worker::Event;
+use worker::{Event, LogEvent, DatabaseEvent};
 
 
 pub trait State {
@@ -30,19 +30,19 @@ pub trait State {
     fn recv(&self) -> Result<serde_json::Value>;
 
     fn info(&self, msg: String) {
-        self.send(&Event::Info(msg))
+        self.send(&Event::Log(LogEvent::Info(msg)))
     }
 
     fn error(&self, msg: String) {
-        self.send(&Event::Error(msg))
+        self.send(&Event::Log(LogEvent::Error(msg)))
     }
 
     fn status(&self, msg: String) {
-        self.send(&Event::Status(msg))
+        self.send(&Event::Log(LogEvent::Status(msg)))
     }
 
     fn db_insert(&self, object: Insert) -> Result<Option<i32>> {
-        self.send(&Event::Insert(object));
+        self.send(&Event::Database(DatabaseEvent::Insert(object)));
         let reply = self.recv()?;
         let reply: result::Result<Option<i32>, String> = serde_json::from_value(reply)?;
 
@@ -50,7 +50,7 @@ pub trait State {
     }
 
     fn db_select(&self, family: Family, value: String) -> Result<Option<i32>> {
-        self.send(&Event::Select((family, value)));
+        self.send(&Event::Database(DatabaseEvent::Select((family, value))));
         let reply = self.recv()?;
         let reply: result::Result<Option<i32>, String> = serde_json::from_value(reply)?;
 
@@ -58,7 +58,7 @@ pub trait State {
     }
 
     fn db_update(&self, object: String, update: Update) -> Result<Option<i32>> {
-        self.send(&Event::Update((object, update)));
+        self.send(&Event::Database(DatabaseEvent::Update((object, update))));
         let reply = self.recv()?;
         let reply: result::Result<Option<i32>, String> = serde_json::from_value(reply)?;
 
diff --git a/src/engine/isolation.rs b/src/engine/isolation.rs
index a1402e94..c425ddfe 100644
--- a/src/engine/isolation.rs
+++ b/src/engine/isolation.rs
@@ -1,15 +1,14 @@
 use errors::*;
-use channel;
 use chrootable_https::dns::DnsConfig;
-use engine::{Environment, Module, Event, Reporter};
+use engine::{Environment, Module, Reporter};
 use geoip::{GeoIP, AsnDB};
 use psl::Psl;
 use serde_json;
+use worker::{Event, Event2, LogEvent, DatabaseEvent, ExitEvent, EventSender};
 
 use std::env;
 use std::io::prelude::*;
 use std::io::{self, BufReader, BufRead, Stdin, Stdout};
-use std::result;
 use std::sync::{mpsc, Arc, Mutex};
 use std::process::{Command, Child, Stdio, ChildStdin, ChildStdout};
 
@@ -98,14 +97,14 @@ impl Supervisor {
         }
     }
 
-    pub fn send_event_callback(&mut self, event: Event, tx: &channel::Sender<(Event, Option<mpsc::Sender<result::Result<Option<i32>, String>>>)>) {
+    pub fn send_event_callback(&mut self, event: DatabaseEvent, tx: &EventSender) {
         let (tx2, rx2) = mpsc::channel();
-        tx.send((event, Some(tx2))).unwrap();
+        tx.send(Event2::Database((event, tx2)));
         let reply = rx2.recv().unwrap();
 
         let value = serde_json::to_value(reply).expect("Failed to serialize reply");
         if let Err(_) = self.send(&value) {
-            tx.send((Event::Error("Failed to send to child".into()), None)).unwrap();
+            tx.send(Event2::Log(LogEvent::Error("Failed to send to child".into())));
         }
     }
 }
@@ -153,7 +152,7 @@ impl Reporter for StdioReporter {
     }
 }
 
-pub fn spawn_module(module: Module, tx: channel::Sender<(Event, Option<mpsc::Sender<result::Result<Option<i32>, String>>>)>, arg: serde_json::Value) -> Result<()> {
+pub fn spawn_module(module: Module, tx: &EventSender, arg: serde_json::Value) -> Result<()> {
     let dns_config = DnsConfig::from_system()?;
 
     let psl = Psl::open_into_string()?;
@@ -163,15 +162,14 @@ pub fn spawn_module(module: Module, tx: channel::Sender<(Event, Option<mpsc::Sen
 
     loop {
         match supervisor.recv()? {
-            Event::Done => break,
-            Event::Fatal(err) => {
-                tx.send((Event::Fatal(err), None)).unwrap();
+            Event::Log(event) => tx.send(Event2::Log(event)),
+            Event::Database(object) => supervisor.send_event_callback(object, &tx),
+            Event::Exit(event) => {
+                if let ExitEvent::Err(err) = event {
+                    tx.send(Event2::Log(LogEvent::Error(err)));
+                }
                 break;
             },
-            Event::Insert(object) => supervisor.send_event_callback(Event::Insert(object), &tx),
-            Event::Select(object) => supervisor.send_event_callback(Event::Select(object), &tx),
-            Event::Update(object) => supervisor.send_event_callback(Event::Update(object), &tx),
-            event => tx.send((event, None)).unwrap(),
         }
     }
 
@@ -200,11 +198,11 @@ pub fn run_worker(geoip: GeoIP, asn: AsnDB) -> Result<()> {
     let mut reporter = Arc::try_unwrap(mtx).expect("Failed to consume Arc")
                         .into_inner().expect("Failed to consume Mutex");
 
-    if let Err(err) = result {
-        reporter.send(&Event::Fatal(err.to_string()))?;
-    } else {
-        reporter.send(&Event::Done)?;
-    }
+    let event = match result {
+        Ok(_) => ExitEvent::Ok,
+        Err(err) => ExitEvent::Err(err.to_string()),
+    };
+    reporter.send(&Event::Exit(event))?;
 
     Ok(())
 }
diff --git a/src/lib.rs b/src/lib.rs
index ae4f4a55..3e03657e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -34,6 +34,7 @@ extern crate toml;
 extern crate maxminddb;
 extern crate tar;
 extern crate libflate;
+extern crate threadpool;
 extern crate serde;
 extern crate serde_json;
 extern crate serde_urlencoded;
diff --git a/src/main.rs b/src/main.rs
index bb19f01c..1170de48 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -40,7 +40,7 @@ fn run_run(gargs: &Args, args: &args::Run, config: Config) -> Result<()> {
         bail!("At least one module or file need to be provided");
     }
 
-    cmd::run_cmd::execute(&mut rl)
+    cmd::run_cmd::execute(&mut rl, args.threads)
 }
 
 fn run_sandbox() -> Result<()> {
diff --git a/src/shell.rs b/src/shell.rs
index 19f92457..ade2825c 100644
--- a/src/shell.rs
+++ b/src/shell.rs
@@ -118,7 +118,7 @@ pub struct Readline {
     psl: Psl,
     config: Config,
     engine: Engine,
-    signal_register: Arc<AtomicUsize>,
+    signal_register: Arc<SignalRegister>,
 }
 
 impl Readline {
@@ -141,7 +141,7 @@ impl Readline {
             psl,
             config,
             engine,
-            signal_register: Arc::new(AtomicUsize::new(1)),
+            signal_register: Arc::new(SignalRegister::new()),
         };
 
         rl.reload_module_cache();
@@ -278,27 +278,42 @@ impl Readline {
             // value of 2 afterwards. If we don't want to catch ctrlcs anymore we set the ctr back to 1.
             // This is important so we can still terminate the process while we are reading input from stdin,
             // eg while waiting for input during `add domain`.
-            let prev = ctr.fetch_add(1, Ordering::SeqCst);
+            let prev = ctr.add_ctrlc();
             if prev == 1 {
                 ::std::process::exit(0);
             }
         }).map_err(Error::from)
     }
 
+    pub fn signal_register(&self) -> &Arc<SignalRegister> {
+        &self.signal_register
+    }
+}
+
+pub struct SignalRegister(AtomicUsize);
+
+impl SignalRegister {
+    pub fn new() -> SignalRegister {
+        SignalRegister(AtomicUsize::new(1))
+    }
+
     pub fn catch_ctrl(&self) {
-        self.signal_register.store(0, Ordering::SeqCst);
+        self.0.store(0, Ordering::SeqCst);
+    }
+
+    pub fn add_ctrlc(&self) -> usize {
+        self.0.fetch_add(1, Ordering::SeqCst)
     }
 
     pub fn ctrlc_received(&self) -> bool {
-        self.signal_register.load(Ordering::SeqCst) == 1
+        self.0.load(Ordering::SeqCst) == 1
     }
 
     pub fn reset_ctrlc(&self) {
-        self.signal_register.store(1, Ordering::SeqCst);
+        self.0.store(1, Ordering::SeqCst);
     }
 }
 
-
 #[inline]
 pub fn print_banner() {
     println!(r#"
diff --git a/src/term.rs b/src/term.rs
index 9784d03b..b1fe13f4 100644
--- a/src/term.rs
+++ b/src/term.rs
@@ -1,6 +1,7 @@
 use db;
 use engine::Module;
 use rand::prelude::*;
+use std::collections::HashMap;
 use std::fmt;
 use std::io;
 use std::io::prelude::*;
@@ -37,7 +38,6 @@ pub static SPINNERS: &[&[&str]] = &[
     &["⠁", "⠉", "⠙", "⠚", "⠒", "⠂", "⠂", "⠒", "⠲", "⠴", "⠤", "⠄", "⠄", "⠤", "⠴", "⠲", "⠒", "⠂", "⠂", "⠒", "⠚", "⠙", "⠉", "⠁"],
     &["⢹", "⢺", "⢼", "⣸", "⣇", "⡧", "⡗", "⡏"],
     &["▙", "▛", "▜", "▟"],
-    &["☱", "☲", "☴"],
     &["▓", "▒", "░", "▒"],
     &["⠂       ", "⠈       ", " ⠂      ", " ⠠      ", "  ⡀     ", "  ⠠     ", "   ⠂    ", "   ⠈    ", "    ⠂   ", "    ⠠   ", "     ⡀  ", "     ⠠  ", "      ⠂ ", "      ⠈ ", "       ⠂", "       ⠠", "       ⡀", "      ⠠ ", "      ⠂ ", "     ⠈  ", "     ⠂  ", "    ⠠   ", "    ⡀   ", "   ⠠    ", "   ⠂    ", "  ⠈     ", "  ⠂     ", " ⠠      ", " ⡀      ", "⠠       "],
     &["|\\____________", "_|\\___________", "__|\\__________", "___|\\_________", "____|\\________", "_____|\\_______", "______|\\______", "_______|\\_____", "________|\\____", "_________|\\___", "__________|\\__", "___________|\\_", "____________|\\", "____________/|", "___________/|_", "__________/|__", "_________/|___", "________/|____", "_______/|_____", "______/|______", "_____/|_______", "____/|________", "___/|_________", "__/|__________", "_/|___________", "/|____________"],
@@ -49,6 +49,14 @@ pub static SPINNERS: &[&[&str]] = &[
     &[" ", "⠁", "⠉", "⠙", "⠚", "⠖", "⠦", "⠤", "⠠"],
 ];
 
+pub trait SpinLogger {
+    fn log(&mut self, line: &str);
+
+    fn error(&mut self, line: &str);
+
+    fn status(&mut self, status: String);
+}
+
 pub struct Spinner {
     indicator: &'static [&'static str],
     status: String,
@@ -64,32 +72,25 @@ impl Spinner {
         }
     }
 
-    pub fn status(&mut self, status: String) {
-        self.status = status;
-    }
-
     pub fn random(status: String) -> Spinner {
-        let indicator = thread_rng().choose(SPINNERS).unwrap();
+        let indicator = SPINNERS.choose(&mut thread_rng()).unwrap();
         Spinner::new(indicator, status)
     }
 
     pub fn tick(&mut self) {
+        print!("{}", self.tick_bytes());
+        io::stdout().flush().unwrap();
+    }
+
+    pub fn tick_bytes(&mut self) -> String {
         if self.i >= self.indicator.len() {
             self.i = 0;
         }
 
-        print!("\r\x1b[1m[\x1b[32m{}\x1b[0;1m]\x1b[0m {}...", self.indicator[self.i], self.status);
-        io::stdout().flush().unwrap();
-
+        let s = format!("\r\x1b[2K\x1b[1m[\x1b[32m{}\x1b[0;1m]\x1b[0m {}...", self.indicator[self.i], self.status);
         self.i += 1;
-    }
-
-    pub fn log(&self, line: &str) {
-        println!("\r\x1b[2K\x1b[1m[\x1b[34m{}\x1b[0;1m]\x1b[0m {}", '*', line);
-    }
 
-    pub fn error(&self, line: &str) {
-        println!("\r\x1b[2K\x1b[1m[\x1b[31m{}\x1b[0;1m]\x1b[0m {}", '-', line);
+        s
     }
 
     pub fn done(&self) {
@@ -105,12 +106,26 @@ impl Spinner {
         print!("\r\x1b[2K");
     }
 
-    pub fn fail(&self, err: &str) {
+    pub fn fail(&mut self, err: &str) {
         self.error(err);
         self.clear();
     }
 }
 
+impl SpinLogger for Spinner {
+    fn log(&mut self, line: &str) {
+        println!("\r\x1b[2K\x1b[1m[\x1b[34m{}\x1b[0;1m]\x1b[0m {}", '*', line);
+    }
+
+    fn error(&mut self, line: &str) {
+        println!("\r\x1b[2K\x1b[1m[\x1b[31m{}\x1b[0;1m]\x1b[0m {}", '-', line);
+    }
+
+    fn status(&mut self, status: String) {
+        self.status = status;
+    }
+}
+
 pub fn success(line: &str) {
     println!("\x1b[1m[\x1b[34m{}\x1b[0;1m]\x1b[0m {}", '*', line);
 }
@@ -153,3 +168,107 @@ impl fmt::Display for Prompt {
         write!(f, " > ")
     }
 }
+
+pub struct StackedSpinners {
+    spinners: HashMap<String, Spinner>,
+    drawn: usize,
+}
+
+impl StackedSpinners {
+    pub fn new() -> StackedSpinners {
+        StackedSpinners {
+            spinners: HashMap::new(),
+            drawn: 0,
+        }
+    }
+
+    pub fn add(&mut self, key: String, status: String) {
+        let s = Spinner::random(status);
+        self.spinners.insert(key, s);
+    }
+
+    pub fn remove(&mut self, key: &String) -> Option<Spinner> {
+        self.spinners.remove(key)
+    }
+
+    pub fn jump2start(&mut self) {
+        if self.drawn > 0 {
+            print!("\r\x1b[2K\x1b[{}A", self.drawn);
+            self.drawn = 0;
+        }
+    }
+
+    pub fn tick(&mut self) {
+        self.jump2start();
+
+        if self.spinners.is_empty() {
+            return;
+        }
+
+        let n = self.spinners.len() -1;
+        for (i, (_, s)) in self.spinners.iter_mut().enumerate() {
+            print!("{}", s.tick_bytes());
+            if i < n {
+                print!("\n");
+                self.drawn += 1;
+            }
+        }
+        io::stdout().flush().unwrap();
+    }
+
+    pub fn is_empty(&self) -> bool {
+        self.spinners.is_empty()
+    }
+
+    pub fn clear(&self) {
+        print!("\r\x1b[2K");
+    }
+
+    pub fn prefixed<I: Into<String>>(&mut self, name: I) -> PrefixedLogger<StackedSpinners> {
+        PrefixedLogger::new(self, name)
+    }
+}
+
+impl SpinLogger for StackedSpinners {
+    fn log(&mut self, line: &str) {
+        self.jump2start();
+        println!("\r\x1b[2K\x1b[1m[\x1b[34m{}\x1b[0;1m]\x1b[0m {}", '*', line);
+    }
+
+    fn error(&mut self, line: &str) {
+        self.jump2start();
+        println!("\r\x1b[2K\x1b[1m[\x1b[31m{}\x1b[0;1m]\x1b[0m {}", '-', line);
+    }
+
+    fn status(&mut self, status: String) {
+        self.error(&format!("TODO: set status: {:?}", status));
+    }
+}
+
+pub struct PrefixedLogger<'a, T: 'a + SpinLogger> {
+    s: &'a mut T,
+    prefix: String,
+}
+
+impl<'a, T: SpinLogger> PrefixedLogger<'a, T> {
+    pub fn new<I: Into<String>>(s: &'a mut T, prefix: I) -> PrefixedLogger<T> {
+        PrefixedLogger {
+            s,
+            prefix: prefix.into(),
+        }
+    }
+}
+
+impl<'a, T: SpinLogger> SpinLogger for PrefixedLogger<'a, T> {
+    fn log(&mut self, line: &str) {
+        self.s.log(&format!("{:50}: {}", self.prefix, line))
+    }
+
+    fn error(&mut self, line: &str) {
+        self.s.error(&format!("{:50}: {}", self.prefix, line))
+    }
+
+    fn status(&mut self, status: String) {
+        self.s.status(format!("{:50}: {}", self.prefix, status))
+    }
+}
diff --git a/src/worker.rs b/src/worker.rs
index 150cfdd6..2adf68ad 100644
--- a/src/worker.rs
+++ b/src/worker.rs
@@ -1,124 +1,238 @@
 use errors::*;
 
 use channel;
-use db::{DbChange, Family};
+use db::{Database, DbChange, Family};
 use engine::{self, Module};
 use models::*;
 use serde_json;
 use shell::Readline;
+use std::result;
+use std::sync::{mpsc, Arc, Mutex};
 use std::time::Duration;
-use std::sync::{Arc, Mutex};
 use std::thread;
-use term::Spinner;
+use term::{Spinner, StackedSpinners, SpinLogger};
+use threadpool::ThreadPool;
 
 
+type DbSender = mpsc::Sender<result::Result<Option<i32>, String>>;
+
 #[derive(Debug, Serialize, Deserialize)]
 pub enum Event {
+    Log(LogEvent),
+    Database(DatabaseEvent),
+    Exit(ExitEvent),
+}
+
+#[derive(Debug)]
+pub enum Event2 {
+    Start,
+    Log(LogEvent),
+    Database((DatabaseEvent, DbSender)),
+    Exit(ExitEvent),
+}
+
+#[derive(Debug)]
+pub struct MultiEvent {
+    name: String,
+    event: Event2,
+}
+
+impl MultiEvent {
+    pub fn new<I: Into<String>>(name: I, event: Event2) -> MultiEvent {
+        MultiEvent {
+            name: name.into(),
+            event,
+        }
+    }
+}
+
+#[derive(Debug, Clone)]
+pub struct EventSender {
+    name: String,
+    tx: channel::Sender<MultiEvent>,
+}
+
+impl EventSender {
+    pub fn new(name: String, tx: channel::Sender<MultiEvent>) -> EventSender {
+        EventSender {
+            name,
+            tx,
+        }
+    }
+
+    pub fn send(&self, event: Event2) {
+        self.tx.send(MultiEvent::new(self.name.clone(), event)).unwrap();
+    }
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub enum ExitEvent {
+    Ok,
+    Err(String),
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub enum LogEvent {
     Info(String),
     Error(String),
-    Fatal(String),
     Status(String),
+}
+
+impl LogEvent {
+    pub fn apply<T: SpinLogger>(self, spinner: &mut T) {
+        match self {
+            LogEvent::Info(info) => spinner.log(&info),
+            LogEvent::Error(error) => spinner.error(&error),
+            LogEvent::Status(status) => spinner.status(status),
+        }
+    }
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub enum DatabaseEvent {
     Insert(Insert),
     Select((Family, String)),
     Update((String, Update)),
-    Done,
 }
 
-pub fn spawn(rl: &mut Readline, module: Module, arg: serde_json::Value, pretty_arg: &Option<String>) {
-    let (tx, rx) = channel::bounded(1);
+impl DatabaseEvent {
+    pub fn apply<T: SpinLogger>(self, tx: DbSender, spinner: &mut T, db: &Database) {
+        match self {
+            DatabaseEvent::Insert(object) => {
+                let result = db.insert_generic(&object);
+                debug!("{:?} => {:?}", object, result);
+                let result = match result {
+                    Ok((DbChange::Insert, id)) => {
+                        // TODO: replace id with actual object(?)
+                        if let Ok(obj) = object.printable(db) {
+                            spinner.log(&obj.to_string());
+                        } else {
+                            spinner.error(&format!("Failed to query necessary fields for {:?}", object));
+                        }
+                        Ok(Some(id))
+                    },
+                    Ok((DbChange::Update(update), id)) => {
+                        // TODO: replace id with actual object(?)
+                        spinner.log(&format!("Updating {:?} ({})", object.value(), update));
+                        Ok(Some(id))
+                    },
+                    Ok((DbChange::None, id)) => Ok(Some(id)),
+                    Err(err) => {
+                        let err = err.to_string();
+                        spinner.error(&err);
+                        Err(err)
+                    },
+                };
 
-    let name = match pretty_arg {
-        Some(pretty_arg) => format!("{} ({:?})", module.canonical(), pretty_arg),
-        None => module.canonical(),
-    };
-    let mut spinner = Spinner::random(format!("Running {}", name));
+                tx.send(result).expect("Failed to send db result to channel");
+            },
+            DatabaseEvent::Select((family, value)) => {
+                let result = db.get_opt(&family, &value)
+                    .map_err(|e| e.to_string());
 
-    let t = thread::spawn(move || {
-        if let Err(err) = engine::isolation::spawn_module(module, tx.clone(), arg) {
-            tx.send((Event::Error(err.to_string()), None)).unwrap();
+                tx.send(result).expect("Failed to send db result to channel");
+            },
+            DatabaseEvent::Update((object, update)) => {
+                let result = db.update_generic(&update);
+                debug!("{:?}: {:?} => {:?}", object, update, result);
+                let result = result
+                    .map(Some)
+                    .map_err(|e| e.to_string());
+
+                if let Err(ref err) = result {
+                    spinner.error(&err);
+                } else {
+                    // TODO: bring this somewhat closer to upsert code
+                    spinner.log(&format!("Updating {:?} ({})", object, update));
+                }
+
+                tx.send(result).expect("Failed to send db result to channel");
+            },
         }
-    });
+    }
+}
+
+pub fn spawn(rl: &mut Readline, module: &Module, args: Vec<(serde_json::Value, Option<String>)>, threads: usize) {
+    // This function hangs if args is empty, so return early if that's the case
+    if args.is_empty() {
+        return;
+    }
+
+    let mut stack = StackedSpinners::new();
 
-    let mut failed = None;
+    let (tx, rx) = channel::bounded(1);
+    let pool = ThreadPool::new(threads);
+
+    let mut expected = 0;
+    for (arg, pretty_arg) in args {
+        let name = match pretty_arg {
+            Some(pretty_arg) => format!("{:?}", pretty_arg),
+            None => module.canonical(),
+        };
+
+        let tx = tx.clone();
+        let module = module.clone();
+        let signal_register = rl.signal_register().clone();
+        pool.execute(move || {
+            let tx = EventSender::new(name, tx);
+
+            if signal_register.ctrlc_received() {
+                tx.send(Event2::Exit(ExitEvent::Ok));
+                return;
+            }
+
+            tx.send(Event2::Start);
+            let event = match engine::isolation::spawn_module(module, &tx, arg) {
+                Ok(_) => ExitEvent::Ok,
+                Err(err) => ExitEvent::Err(err.to_string()),
+            };
+            tx.send(Event2::Exit(event));
+        });
+        expected += 1;
+    }
+
+    let mut failed = Vec::new();
     let timeout = Duration::from_millis(100);
     loop {
         select! {
             recv(rx) -> msg => match msg.ok() {
-                Some((Event::Info(info), _)) => spinner.log(&info),
-                Some((Event::Error(error), _)) => spinner.error(&error),
-                Some((Event::Fatal(error), _)) => {
-                    failed = Some(error);
-                    break;
-                },
-                Some((Event::Status(status), _)) => spinner.status(status),
-                Some((Event::Insert(object), tx)) => {
-                    let result = rl.db().insert_generic(&object);
-                    debug!("{:?} => {:?}", object, result);
-                    let result = match result {
-                        Ok((DbChange::Insert, id)) => {
-                            // TODO: replace id with actual object(?)
-                            if let Ok(obj) = object.printable(rl.db()) {
-                                spinner.log(&obj.to_string());
-                            } else {
-                                spinner.error(&format!("Failed to query necessary fields for {:?}", object));
-                            }
-                            Ok(Some(id))
-                        },
-                        Ok((DbChange::Update(update), id)) => {
-                            // TODO: replace id with actual object(?)
-                            spinner.log(&format!("Updating {:?} ({})", object.value(), update));
-                            Ok(Some(id))
-                        },
-                        Ok((DbChange::None, id)) => Ok(Some(id)),
-                        Err(err) => {
-                            let err = err.to_string();
-                            spinner.error(&err);
-                            Err(err)
-                        },
-                    };
+                Some(event) => {
+                    let (name, event) = (event.name, event.event);
 
-                    tx.expect("Failed to get db result channel")
-                        .send(result).expect("Failed to send db result to channel");
-                },
-                Some((Event::Select((family, value)), tx)) => {
-                    let result = rl.db().get_opt(&family, &value)
-                        .map_err(|e| e.to_string());
+                    match event {
+                        Event2::Start => {
+                            let label = format!("Investigating {}", name);
+                            stack.add(name, label);
+                        },
+                        Event2::Log(log) => log.apply(&mut stack.prefixed(name)),
+                        Event2::Database((db, tx)) => db.apply(tx, &mut stack.prefixed(name), rl.db()),
+                        Event2::Exit(event) => {
+                            stack.remove(&name);
+                            if let ExitEvent::Err(error) = event {
+                                failed.push((name, error));
+                            }
 
-                    tx.expect("Failed to get db result channel")
-                        .send(result).expect("Failed to send db result to channel");
-                },
-                Some((Event::Update((object, update)), tx)) => {
-                    let result = rl.db().update_generic(&update);
-                    debug!("{:?}: {:?} => {:?}", object, update, result);
-                    let result = result
-                        .map(Some)
-                        .map_err(|e| e.to_string());
-
-                    if let Err(ref err) = result {
-                        spinner.error(&err);
-                    } else {
-                        // TODO: bring this somewhat closer to upsert code
-                        spinner.log(&format!("Updating {:?} ({})", object, update));
+                            // if every task reported back, exit
+                            expected -= 1;
+                            info!("spawn_all is expecting {} more results", expected);
+                            if expected == 0 {
+                                break;
+                            }
+                        },
                     }
-
-                    tx.expect("Failed to get db result channel")
-                        .send(result).expect("Failed to send db result to channel");
                 },
-                Some((Event::Done, _)) => break,
                 None => break, // channel closed
             },
             default(timeout) => (),
         }
-        spinner.tick();
+        stack.tick();
     }
 
-    t.join().expect("thread failed");
-
-    if let Some(fail) = failed {
-        spinner.fail(&format!("Failed {}: {}", name, fail));
-    } else {
-        spinner.clear();
+    for (name, fail) in failed {
+        stack.error(&format!("Failed {}: {}", name, fail));
     }
+
+    stack.clear();
 }
 
 pub fn spawn_fn<F, T>(label: &str, f: F, clear: bool) -> Result<T>
@@ -134,14 +248,11 @@ pub fn spawn_fn<F, T>(label: &str, f: F, clear: bool) -> Result<T>
         loop {
             select! {
                 recv(rx) -> msg => match msg.ok() {
-                    Some(Event::Info(info)) => spinner.log(&info),
-                    Some(Event::Error(error)) => spinner.error(&error),
-                    Some(Event::Fatal(error)) => spinner.error(&error),
-                    Some(Event::Status(status)) => spinner.status(status),
-                    Some(Event::Insert(_)) => (),
-                    Some(Event::Select(_)) => (),
-                    Some(Event::Update(_)) => (),
-                    Some(Event::Done) => break,
+                    Some(Event::Log(log)) => log.apply(&mut *spinner),
+                    Some(Event::Database(_)) => (),
+                    // TODO: refactor
+                    Some(Event::Exit(ExitEvent::Ok)) => break,
+                    Some(Event::Exit(ExitEvent::Err(error))) => spinner.error(&error),
                     None => break, // channel closed
                 },
                 default(timeout) => (),
@@ -152,7 +263,7 @@ pub fn spawn_fn<F, T>(label: &str, f: F, clear: bool) -> Result<T>
 
     // run work in main thread
     let result = f()?;
-    tx.send(Event::Done)?;
+    tx.send(Event::Exit(ExitEvent::Ok))?;
 
     t.join().expect("thread failed");
 


Test patch: