Repository: tarkah/tickrs (language: rust)

Issue:
Panic: Too many open files
```
Backtrace (most recent call first):
  File "rust:library/core/src/option.rs", line 1268, in core::option::expect_none_failed
  File "<unknown>", line 0, in tickrs_api::client::Client::new
  File "<unknown>", line 0, in tickrs::task::AsyncTask::connect
  File "<unknown>", line 0, in tickrs::event::handle_keys_display_summary
  File "<unknown>", line 0, in tickrs::main
  File "<unknown>", line 0, in std::sys_common::backtrace::__rust_begin_short_backtrace
  File "<unknown>", line 0, in std::rt::lang_start::{{closure}}
  File "rust:library/core/src/ops/function.rs", line 259, in core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &F>::call_once
  File "rust:library/std/src/panicking.rs", line 379, in std::panicking::try::do_call
  File "rust:library/std/src/panicking.rs", line 343, in std::panicking::try
  File "rust:library/std/src/panic.rs", line 396, in std::panic::catch_unwind
  File "rust:library/std/src/rt.rs", line 51, in std::rt::lang_start_internal
  File "<unknown>", line 0, in main
  File "<unknown>", line 0, in __libc_start_main
  File "<unknown>", line 0, in _start

The application panicked (crashed).
  called `Result::unwrap()` on an `Err` value: Io(Os { code: 24, kind: Other, message: "Too many open files" })
in /tickrs-api-0.9.0/src/client.rs, line 193
thread: main
```


Discussion / hints:
After some digging into the code, a `Result::Err` was returned by `isahc` because of the reinitialization of HTTP clients for every task which `isahc` itself warns against as it's expensive and inefficient [docs.rs [isahc::HttpClient]](https://docs.rs/isahc/1.1.0/isahc/struct.HttpClient.html#toggle-all-docs:~:text=The%20client%20maintains%20a%20connection%20pool,system%20resources%20like%20sockets%20or%20threads.)

A fix for this would be statically creating a crate-wide instantiation of the `Client` that's shared across all tasks.
Yeah good call, I'll add that to the api crate. It's interesting because I had this same too many files open error early last year when I first started this project, but it was an upstream bug with the crossterm crate we use. I was originally thinking iti might be from that again.

Thanks for digging into this! 

Fix patch:
diff --git a/src/main.rs b/src/main.rs
index 5ad64d2..d6157d4 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -27,6 +27,7 @@ mod task;
 mod widget;
 
 lazy_static! {
+    static ref CLIENT: api::Client = api::Client::new();
     pub static ref OPTS: cli::Opt = cli::get_opts();
     pub static ref UPDATE_INTERVAL: u64 = OPTS.update_interval;
     pub static ref TIME_FRAME: TimeFrame = OPTS.time_frame;
diff --git a/src/task/company.rs b/src/task/company.rs
index 224269c..f14d2a4 100644
--- a/src/task/company.rs
+++ b/src/task/company.rs
@@ -16,7 +16,7 @@ impl Company {
 }
 
 impl AsyncTask for Company {
-    type Input = (String, api::Client);
+    type Input = String;
     type Response = CompanyData;
 
     fn update_interval(&self) -> Option<Duration> {
@@ -24,15 +24,14 @@ impl AsyncTask for Company {
     }
 
     fn input(&self) -> Self::Input {
-        (self.symbol.clone(), api::Client::new())
+        self.symbol.clone()
     }
 
     fn task<'a>(input: Arc<Self::Input>) -> BoxFuture<'a, Option<Self::Response>> {
         Box::pin(async move {
-            let symbol = &input.0;
-            let client = &input.1;
+            let symbol = input.as_ref();
 
-            client.get_company_data(symbol).await.ok()
+            crate::CLIENT.get_company_data(symbol).await.ok()
         })
     }
 }
diff --git a/src/task/current_price.rs b/src/task/current_price.rs
index 9a7c4ff..084b397 100644
--- a/src/task/current_price.rs
+++ b/src/task/current_price.rs
@@ -15,7 +15,7 @@ impl CurrentPrice {
 }
 
 impl AsyncTask for CurrentPrice {
-    type Input = (String, api::Client);
+    type Input = String;
     type Response = (f64, Option<f64>, String);
 
     fn update_interval(&self) -> Option<Duration> {
@@ -23,15 +23,14 @@ impl AsyncTask for CurrentPrice {
     }
 
     fn input(&self) -> Self::Input {
-        (self.symbol.clone(), api::Client::new())
+        self.symbol.clone()
     }
 
     fn task<'a>(input: Arc<Self::Input>) -> BoxFuture<'a, Option<Self::Response>> {
         Box::pin(async move {
-            let symbol = &input.0;
-            let client = &input.1;
+            let symbol = input.as_ref();
 
-            if let Ok(response) = client.get_company_data(symbol).await {
+            if let Ok(response) = crate::CLIENT.get_company_data(symbol).await {
                 let regular_price = response.price.regular_market_price.price;
 
                 let post_price = response.price.post_market_price.price;
diff --git a/src/task/default_timestamps.rs b/src/task/default_timestamps.rs
index 120f5f7..f397a3e 100644
--- a/src/task/default_timestamps.rs
+++ b/src/task/default_timestamps.rs
@@ -29,25 +29,19 @@ impl AsyncTask for DefaultTimestamps {
 
     fn task<'a>(_input: Arc<Self::Input>) -> BoxFuture<'a, Option<Self::Response>> {
         Box::pin(async move {
-            let client = Arc::new(api::Client::new());
-
             let symbol = "SPY";
 
-            let tasks = TimeFrame::ALL[1..].iter().map(|timeframe| {
-                let client_clone = client.clone();
-
-                async move {
-                    let interval = timeframe.api_interval();
-                    let range = timeframe.as_range();
-
-                    if let Ok(chart) = client_clone
-                        .get_chart_data(symbol, interval, range, false)
-                        .await
-                    {
-                        Some((*timeframe, chart.timestamp))
-                    } else {
-                        None
-                    }
+            let tasks = TimeFrame::ALL[1..].iter().map(|timeframe| async move {
+                let interval = timeframe.api_interval();
+                let range = timeframe.as_range();
+
+                if let Ok(chart) = crate::CLIENT
+                    .get_chart_data(symbol, interval, range, false)
+                    .await
+                {
+                    Some((*timeframe, chart.timestamp))
+                } else {
+                    None
                 }
             });
 
diff --git a/src/task/options_data.rs b/src/task/options_data.rs
index 405ce76..ed77e96 100644
--- a/src/task/options_data.rs
+++ b/src/task/options_data.rs
@@ -19,7 +19,7 @@ impl OptionsData {
 }
 
 impl AsyncTask for OptionsData {
-    type Input = (String, i64, api::Client);
+    type Input = (String, i64);
     type Response = model::OptionsHeader;
 
     fn update_interval(&self) -> Option<Duration> {
@@ -27,16 +27,15 @@ impl AsyncTask for OptionsData {
     }
 
     fn input(&self) -> Self::Input {
-        (self.symbol.clone(), self.date, api::Client::new())
+        (self.symbol.clone(), self.date)
     }
 
     fn task<'a>(input: Arc<Self::Input>) -> BoxFuture<'a, Option<Self::Response>> {
         Box::pin(async move {
             let symbol = &input.0;
             let date = input.1;
-            let client = &input.2;
 
-            client
+            crate::CLIENT
                 .get_options_for_expiration_date(symbol, date)
                 .await
                 .ok()
diff --git a/src/task/options_dates.rs b/src/task/options_dates.rs
index dc0ec26..85a1742 100644
--- a/src/task/options_dates.rs
+++ b/src/task/options_dates.rs
@@ -17,7 +17,7 @@ impl OptionsDates {
 }
 
 impl AsyncTask for OptionsDates {
-    type Input = (String, api::Client);
+    type Input = String;
     type Response = Vec<i64>;
 
     fn update_interval(&self) -> Option<Duration> {
@@ -25,15 +25,17 @@ impl AsyncTask for OptionsDates {
     }
 
     fn input(&self) -> Self::Input {
-        (self.symbol.clone(), api::Client::new())
+        self.symbol.clone()
     }
 
     fn task<'a>(input: Arc<Self::Input>) -> BoxFuture<'a, Option<Self::Response>> {
         Box::pin(async move {
-            let symbol = &input.0;
-            let client = &input.1;
+            let symbol = input.as_ref();
 
-            client.get_options_expiration_dates(symbol).await.ok()
+            crate::CLIENT
+                .get_options_expiration_dates(symbol)
+                .await
+                .ok()
         })
     }
 }
diff --git a/src/task/prices.rs b/src/task/prices.rs
index 5dea9b8..5e7dd46 100644
--- a/src/task/prices.rs
+++ b/src/task/prices.rs
@@ -18,7 +18,7 @@ impl Prices {
 }
 
 impl AsyncTask for Prices {
-    type Input = (String, TimeFrame, api::Client);
+    type Input = (String, TimeFrame);
     type Response = (TimeFrame, ChartMeta, Vec<Price>);
 
     fn update_interval(&self) -> Option<Duration> {
@@ -26,20 +26,19 @@ impl AsyncTask for Prices {
     }
 
     fn input(&self) -> Self::Input {
-        (self.symbol.clone(), self.time_frame, api::Client::new())
+        (self.symbol.clone(), self.time_frame)
     }
 
     fn task<'a>(input: Arc<Self::Input>) -> BoxFuture<'a, Option<Self::Response>> {
         Box::pin(async move {
             let symbol = &input.0;
             let time_frame = input.1;
-            let client = &input.2;
 
             let interval = time_frame.api_interval();
 
             let include_pre_post = time_frame == TimeFrame::Day1;
 
-            if let Ok(response) = client
+            if let Ok(response) = crate::CLIENT
                 .get_chart_data(&symbol, interval, time_frame.as_range(), include_pre_post)
                 .await
             {


Test patch: