| 253 | } |
| 254 | |
| 255 | void HdfsScanNode::ThreadTokenAvailableCb(ThreadResourcePool* pool) { |
| 256 | // This is called to start up new scanner threads. It's not a big deal if we |
| 257 | // spin up more than strictly necessary since they will go through and terminate |
| 258 | // promptly. However, we want to minimize that by checking a conditions. |
| 259 | // 1. Don't start up if the ScanNode is done |
| 260 | // 2. Don't start up if all the ranges have been taken by another thread. |
| 261 | // 3. Don't start up if the number of ranges left is less than the number of |
| 262 | // active scanner threads. |
| 263 | // 4. Don't start up if no initial ranges have been issued (see IMPALA-1722). |
| 264 | // 5. Don't start up a ScannerThread if the row batch queue is full since |
| 265 | // we are not scanner bound. |
| 266 | // 6. Don't start up a thread if there is not enough memory available for the |
| 267 | // estimated memory consumption (include reservation and non-reserved memory). |
| 268 | // 7. Don't start up a thread if it is an extra thread and we can't reserve another |
| 269 | // minimum reservation's worth of memory for the thread. |
| 270 | // 8. Don't start up more than maximum number of scanner threads configured. |
| 271 | // 9. Don't start up if there are no thread tokens. |
| 272 | |
| 273 | // Case 4. We have not issued the initial ranges so don't start a scanner thread. |
| 274 | // Issuing ranges will call this function and we'll start the scanner threads then. |
| 275 | // TODO: It would be good to have a test case for that. |
| 276 | if (!initial_ranges_issued_.Load()) return; |
| 277 | |
| 278 | ScannerMemLimiter* scanner_mem_limiter = |
| 279 | runtime_state_->query_state()->scanner_mem_limiter(); |
| 280 | Status status = Status::OK(); |
| 281 | while (true) { |
| 282 | // The lock must be given up between loops in order to give writers to done_, |
| 283 | // all_ranges_started_ etc. a chance to grab the lock. |
| 284 | // IMPALA-8322: Another thread can hold this lock for significant periods of time |
| 285 | // if the scan node is being cancelled. Since this function can be called while |
| 286 | // holding other locks that can block other threads (e.g. ThreadResourceMgr::lock_), |
| 287 | // avoid blocking unnecessarily. There are two remedies. First, we do a check of |
| 288 | // done() to try to avoid acquiring the lock_, as there is nothing to do if |
| 289 | // the scan node is done. Second, this uses a timeout of 10 milliseconds when |
| 290 | // acquiring the lock_ to allow a periodic check of done(). The 10 millisecond |
| 291 | // timeout is arbitrary. |
| 292 | // TODO: This still leans heavily on starvation-free locks, come up with a more |
| 293 | // correct way to communicate between this method and ScannerThread(). |
| 294 | if (done()) break; |
| 295 | unique_lock<timed_mutex> lock(lock_, std::chrono::milliseconds(10)); |
| 296 | if (!lock.owns_lock()) { |
| 297 | continue; |
| 298 | } |
| 299 | |
| 300 | const int64_t num_active_scanner_threads = thread_state_.GetNumActive(); |
| 301 | const bool first_thread = num_active_scanner_threads == 0; |
| 302 | const int64_t est_mem = thread_state_.estimated_per_thread_mem(); |
| 303 | const int64_t scanner_thread_reservation = resource_profile_.min_reservation; |
| 304 | // Cases 1, 2, 3. |
| 305 | if (done() || all_ranges_started_ || |
| 306 | num_active_scanner_threads >= shared_state_->progress().remaining()) { |
| 307 | break; |
| 308 | } |
| 309 | |
| 310 | if (!first_thread) { |
| 311 | // Cases 5, 6 and 7. |
| 312 | if (thread_state_.batch_queue()->IsFull()) break; |
nothing calls this directly
no test coverage detected