# Buggy code
def _do_request(self, method, path, query_string=None, headers=None, body=None):
    """Send request, read and return response object"""

    log.debug("started with %s %s, qs=%s", method, path, query_string)

    if headers is None:
        headers = CaseInsensitiveDict()

    expect100 = isinstance(body, BodyFollowing)
    headers["host"] = self.hostname
    if query_string:
        s = urllib.parse.urlencode(query_string, doseq=True)
        path += "?%s" % s

    # If we have an access token, try to use it.
    token = self.access_token.get(self.refresh_token, None)
    if token is not None:
        headers["Authorization"] = "Bearer " + token
        self.conn.send_request(
            method, path, body=body, headers=headers, expect100=expect100
        )
        resp = self.conn.read_response()
        if (expect100 and resp.status == 100) or (
            not expect100 and 200 <= resp.status <= 299
        ):
            return resp
        elif resp.status != 401:
            raise self._parse_error_response(resp)

    # If we reach this point, then the access token must have
    # expired, so we try to get a new one. We use a lock to prevent
    # multiple threads from refreshing the token simultaneously.
    with self._refresh_lock:
        # Don't refresh if another thread has already done so while
        # we waited for the lock.
        if token is None or self.access_token.get(self.refresh_token, None) == token:
            self._get_access_token()

    # Try request again. If this still fails, propagate the error
    # (because we have just refreshed the access token).
    # FIXME: We can't rely on this if e.g. the system hibernated
    # after refreshing the token, but before reaching this line.
    headers["Authorization"] = "Bearer " + self.access_token[self.refresh_token]
    self.conn.send_request(
        method, path, body=body, headers=headers, expect100=expect100
    )
    resp = self.conn.read_response()
    if (expect100 and resp.status == 100) or (
        not expect100 and 200 <= resp.status <= 299
    ):
        return resp
    else:
        raise self._parse_error_response(resp)


# Traceback (s3ql.backends.gs.RequestError)
2019-02-20 18:51:00.053 1467:MainThread s3ql.mount.determine_threads: Using 4 upload threads.
2019-02-20 18:51:00.054 1467:MainThread s3ql.mount.main: Autodetected 1048532 file descriptors available for cache entries
2019-02-20 18:51:00.247 1467:MainThread s3ql.backends.gs._get_access_token: Requesting new access token
2019-02-20 18:51:06.003 1467:MainThread s3ql.mount.get_metadata: Using cached metadata.
2019-02-20 18:51:06.007 1467:MainThread s3ql.mount.main: Setting cache size to 55240 MB
2019-02-20 18:51:06.008 1467:MainThread s3ql.mount.main: Mounting gs://bucket/main at /mnt/bucketmain...
2019-02-20 18:51:06.015 1473:MainThread s3ql.daemonize.detach_process_context: Daemonizing, new PID is 1474
2019-02-20 19:55:08.263 1474:Thread-6 root.excepthook: Uncaught top-level exception:
Traceback (most recent call last):
File "/root/.virtualenvs/s3ql/lib/python3.6/site-packages/s3ql/mount.py", line 58, in run_with_except_hook
run_old(*args, **kw)
File "/usr/lib/python3.6/threading.py", line 864, in run
self._target(*self._args, **self._kwargs)
File "/root/.virtualenvs/s3ql/lib/python3.6/site-packages/s3ql/block_cache.py", line 445, in _upload_loop
self._do_upload(*tmp)
File "/root/.virtualenvs/s3ql/lib/python3.6/site-packages/s3ql/block_cache.py", line 472, in _do_upload
% obj_id).get_obj_size()
File "/root/.virtualenvs/s3ql/lib/python3.6/site-packages/s3ql/backends/common.py", line 108, in wrapped
return method(*a, **kw)
File "/root/.virtualenvs/s3ql/lib/python3.6/site-packages/s3ql/backends/common.py", line 279, in perform_write
return fn(fh)
File "/root/.virtualenvs/s3ql/lib/python3.6/site-packages/s3ql/backends/comprenc.py", line 389, in __exit__
self.close()
File "/root/.virtualenvs/s3ql/lib/python3.6/site-packages/s3ql/backends/comprenc.py", line 383, in close
self.fh.close()
File "/root/.virtualenvs/s3ql/lib/python3.6/site-packages/s3ql/backends/comprenc.py", line 548, in close
self.fh.close()
File "/root/.virtualenvs/s3ql/lib/python3.6/site-packages/s3ql/backends/gs.py", line 933, in close
self.metadata, size=self.obj_size)
File "/root/.virtualenvs/s3ql/lib/python3.6/site-packages/s3ql/backends/common.py", line 108, in wrapped
return method(*a, **kw)
File "/root/.virtualenvs/s3ql/lib/python3.6/site-packages/s3ql/backends/gs.py", line 485, in write_fh
raise _map_request_error(exc, key) or exc
s3ql.backends.gs.RequestError: <RequestError, code=401, reason='Unauthorized', message='Invalid Credentials'>
2019-02-20 19:55:08.321 1474:Thread-5 s3ql.mount.exchook: Unhandled top-level exception during shutdown (will not be re-raised)
2019-02-20 19:55:08.322 1474:Thread-5 root.excepthook: Uncaught top-level exception:

# Fixed code
def _do_request(self, method, path, query_string=None, headers=None, body=None):
    """Send request, read and return response object"""

    log.debug("started with %s %s, qs=%s", method, path, query_string)

    if headers is None:
        headers = CaseInsensitiveDict()

    expect100 = isinstance(body, BodyFollowing)
    headers["host"] = self.hostname
    if query_string:
        s = urllib.parse.urlencode(query_string, doseq=True)
        path += "?%s" % s

    # If we have an access token, try to use it.
    token = self.access_token.get(self.refresh_token, None)
    if token is not None:
        headers["Authorization"] = "Bearer " + token
        self.conn.send_request(
            method, path, body=body, headers=headers, expect100=expect100
        )
        resp = self.conn.read_response()
        if (expect100 and resp.status == 100) or (
            not expect100 and 200 <= resp.status <= 299
        ):
            return resp
        elif resp.status != 401:
            raise self._parse_error_response(resp)
        self.conn.discard()

    # If we reach this point, then the access token must have
    # expired, so we try to get a new one. We use a lock to prevent
    # multiple threads from refreshing the token simultaneously.
    with self._refresh_lock:
        # Don't refresh if another thread has already done so while
        # we waited for the lock.
        if token is None or self.access_token.get(self.refresh_token, None) == token:
            self._get_access_token()

    # Try request again. If this still fails, propagate the error
    # (because we have just refreshed the access token).
    # FIXME: We can't rely on this if e.g. the system hibernated
    # after refreshing the token, but before reaching this line.
    headers["Authorization"] = "Bearer " + self.access_token[self.refresh_token]
    self.conn.send_request(
        method, path, body=body, headers=headers, expect100=expect100
    )
    resp = self.conn.read_response()
    if (expect100 and resp.status == 100) or (
        not expect100 and 200 <= resp.status <= 299
    ):
        return resp
    else:
        raise self._parse_error_response(resp)