DEV Community

djmitche
djmitche

Posted on

Chromium Spelunking: The IO Thread

The error at the end of the last post looks like this (I've omitted further lines of backtrace as they're not relevant):

[0605/175824.724186:ERROR:churl_bin.cc(190)] URLRequestContext created: 0x7fb224002df0
[0605/175824.725417:ERROR:churl_bin.cc(198)] calling start     
[0605/175824.730763:ERROR:churl_bin.cc(200)] started     
[0605/175824.734067:FATAL:current_thread.cc(197)] Check failed: sequence_manager. 
#0 0x7fb23107ca8c base::debug::CollectStackTrace()
#1 0x7fb2310332da base::debug::StackTrace::StackTrace()                                                
#2 0x7fb231033295 base::debug::StackTrace::StackTrace()       
#3 0x7fb230d575f9 logging::LogMessage::~LogMessage()           
#4 0x7fb230d02bac logging::(anonymous namespace)::DCheckLogMessage::~DCheckLogMessage()
#5 0x7fb230d02bd9 logging::(anonymous namespace)::DCheckLogMessage::~DCheckLogMessage()
#6 0x7fb230d028bd logging::CheckError::~CheckError() 
#7 0x7fb230ed6cc2 base::CurrentIOThread::Get()     
#8 0x7fb232194f2d net::SocketPosix::Connect()                                                            
#9 0x7fb232199906 net::TCPSocketPosix::Connect()                                                         
Enter fullscreen mode Exit fullscreen mode

Looking at the failing DHCECK:

CurrentIOThread CurrentIOThread::Get() {
  auto* sequence_manager = GetCurrentSequenceManagerImpl();
  DCHECK(sequence_manager);
  DCHECK(sequence_manager->IsType(MessagePumpType::IO));
  return CurrentIOThread(sequence_manager);
}
Enter fullscreen mode Exit fullscreen mode

suggests that this is complaining that it's not running in the IO thread. That seems reasonable -- SocketPosix::Connect is, indeed, an IO operation. URLRequest is a pretty low-level tool, and other components are generally expected to use URLFetcher instead. Still, it's notable that URLRequest documents that all uses must be in the same thread but not that the thread must be the IO thread.

OK, Run It On the IO Thread

I suppose the easiest thing to do is run the whole Fetch method on the IO thread. It took me some time to figure out how to create an IO thread. I tried using TestIOThread but it's not defined in non-test cases. However, its implementation is simple enough that I can just duplicate it:

int main(int argc, char *argv[]) {
  // ...
  base::Thread io_thread("IO Thread");
  CHECK(io_thread.StartWithOptions(
      base::Thread::Options(base::MessagePumpType::IO, 0)));
  io_thread.task_runner()->PostTask(
      FROM_HERE, base::BindOnce(&Churl::Fetch, base::Unretained(&churl)));
  base::PlatformThread::Sleep(base::Seconds(5));                                                                                                                                                                   }
Enter fullscreen mode Exit fullscreen mode

Amazingly, the request completes!

[0605/193023.588918:ERROR:churl_bin.cc(68)] OnConnected
[0605/193025.549050:ERROR:churl_bin.cc(144)] OnResponseStarted
[0605/193025.549318:ERROR:churl_bin.cc(154)] Read completed immediately
[0605/193025.549342:ERROR:churl_bin.cc(170)] OnReadCompleted with 281 bytes_read
[0605/193025.549373:ERROR:churl_bin.cc(172)] GOT: {
  "args": {}, 
  "headers": {
    "Accept-Encoding": "gzip, deflate", 
    "Host": "httpbin.org", 
    "User-Agent": "Dustin's Experiment", 
    "X-Amzn-Trace-Id": "Root=1-647e37cf-251697116275c21857a3f844"
  }, 
  "origin": "34.86.234.200", 
  "url": "http://httpbin.org/get"
}
Enter fullscreen mode Exit fullscreen mode

So that's pretty cool! 🥳

A Bad Ending

Unfortunately, once that 5-second sleep expires:

[0605/193351.314239:FATAL:thread_checker.cc(21)] Check failed: checker.CalledOnValidThread(&bound_at). 
#0 0x7fa54a87ca8c base::debug::CollectStackTrace()
#1 0x7fa54a8332da base::debug::StackTrace::StackTrace()
#2 0x7fa54a833295 base::debug::StackTrace::StackTrace()
#3 0x7fa54a5575f9 logging::LogMessage::~LogMessage()
#4 0x7fa54a502bac logging::(anonymous namespace)::DCheckLogMessage::~DCheckLogMessage()
#5 0x7fa54a502bd9 logging::(anonymous namespace)::DCheckLogMessage::~DCheckLogMessage()
#6 0x7fa54a5028bd logging::CheckError::~CheckError()
#7 0x7fa54a7b9029 base::ScopedValidateThreadChecker::ScopedValidateThreadChecker()
#8 0x7fa54b89bdeb net::URLRequest::~URLRequest()
#9 0x7fa54b89c229 net::URLRequest::~URLRequest()
#10 0x55723b34422c std::__Cr::default_delete<>::operator()()
#11 0x55723b34414a std::__Cr::unique_ptr<>::reset()
#12 0x55723b343759 std::__Cr::unique_ptr<>::~unique_ptr()
#13 0x55723b3415aa Churl::~Churl()
#14 0x55723b340ccd main
Enter fullscreen mode Exit fullscreen mode

So main is returning, at which point it destroys the Churl instance, which is destroying the URLRequest instance, which was created on the IO thread. Chromium generally expects objects to be used (and destroyed) in a single thread, and that's what the thread-checker is checking.

I suspect that one way to fix this would be to refactor things so that the Churl instance is destroyed on the IO thread. But that actually doesn't sound like a very interesting challenge -- it's probably easiest in this experiment to just call exit(0) when the fetch is done (in OnReadComplete).

By the way, I wondered if I could drop the thread pool now that everything's in the IO thread. But, no, at least one thing involved in fetching a URL requires a thread pool - net::RunHaveOnlyLoopbackAddressesJob. So, we'll need both.

What's Next?

So, we've successfully fetched a URL (and learned some stuff along the way). If you'd like to see the code, it's in this CL. It's not pretty, and please don't emulate it, but maybe it helps clarify something in one of these posts that wasn't clear.

Next up, I'd like to start learning how proxies are used in this process. That will involve a new delegate, at least. Once I've got a basic HTTP proxy working, I'll want to experiment with various HTTP versions inside and outside of the proxy (HTTP/2 over HTTP/3, etc.).

Top comments (0)