Arrow: Problem with ThreadPoolExecutor

Created on 13 Feb 2019  路  4Comments  路  Source: arrow-py/arrow

I seem to found an issue with concurrent calls to arrow.get.

I assumed that arrow is thread safe but this snippet fails:

from concurrent.futures import ThreadPoolExecutor
import arrow


def doIt():
    try:
        return arrow.get('2019-02-13T04:26:47-0500')
    except Exception as e:
        print(e.message)


if __name__ == "__main__":
    with ThreadPoolExecutor(max_workers=100) as executor:
        while True:
            executor.submit(doIt)
    time.sleep(100)

We are using 0.5.4 which throws:

Could not match input to any of ['YYYY-MM-DDTHH:mm:ssZ'] on '2019-02-13T04:26:47-0500'
Could not match input to any of ['YYYY-MM-DDTHH:mm:ssZ'] on '2019-02-13T04:26:47-0500'
Could not match input to any of ['YYYY-MM-DDTHH:mm:ssZ'] on '2019-02-13T04:26:47-0500'
Could not match input to any of ['YYYY-MM-DDTHH:mm:ssZ'] on '2019-02-13T04:26:47-0500'
Could not match input to any of ['YYYY-MM-DDTHH:mm:ssZ'] on '2019-02-13T04:26:47-0500'

The latest version of 0.13.1 throws an even more cryptic error:

(None, -18000)
(None, -18000)
(None, -18000)
(None, -18000)
(None, -18000)
(None, -18000)

This only happens on a very low percentage of the calls (~0.05%).

Is this a known issue? Is it arrow not thread safe?
I'm running python 2.7

Most helpful comment

The problem is related to the timezone handling. Arrow is using tz.tzoffset which internally caches values in a dictionary. Access to the dictionary is not thread safe so in certain cases, the entry is removed and then accessed by another thread which leads to the KeyError. Example stack trace below:

  Traceback (most recent call last):
  File "/usr/src/app/metrics_results_writer/message_schema.py", line 43, in _extract_content
    timestamp = arrow.get(string_timestamp)
  File "/usr/local/lib/python2.7/site-packages/arrow/api.py", line 22, in get
    return _factory.get(*args, **kwargs)
  File "/usr/local/lib/python2.7/site-packages/arrow/factory.py", line 174, in get
    dt = parser.DateTimeParser(locale).parse_iso(arg)
  File "/usr/local/lib/python2.7/site-packages/arrow/parser.py", line 119, in parse_iso
    return self._parse_multiformat(string, formats)
  File "/usr/local/lib/python2.7/site-packages/arrow/parser.py", line 277, in _parse_multiformat
    _datetime = self.parse(string, fmt)
  File "/usr/local/lib/python2.7/site-packages/arrow/parser.py", line 182, in parse
    self._parse_token(token, value, parts)
  File "/usr/local/lib/python2.7/site-packages/arrow/parser.py", line 235, in _parse_token
    parts['tzinfo'] = TzinfoParser.parse(value)
  File "/usr/local/lib/python2.7/site-packages/arrow/parser.py", line 336, in parse
    tzinfo = tz.tzoffset(None, seconds)
  File "/usr/local/lib/python2.7/site-packages/dateutil/tz/_factories.py", line 40, in __call__
    cls.__strong_cache[key] = cls.__strong_cache.pop(key, instance)
  File "/usr/local/lib/python2.7/collections.py", line 157, in pop
    del self[key]
  File "/usr/local/lib/python2.7/collections.py", line 86, in __delitem__
    link_prev, link_next, _ = self.__map.pop(key)
KeyError: (None, 0)

All 4 comments

Any news on this?

Hi @fbergero I'm not particularly familiar with thread safety in python, but I think it would be wise to assume that arrow isn't thread safe for now.

The output from 0.13.1 doesn't look like arrow error messages, maybe they're from something else?

The problem is related to the timezone handling. Arrow is using tz.tzoffset which internally caches values in a dictionary. Access to the dictionary is not thread safe so in certain cases, the entry is removed and then accessed by another thread which leads to the KeyError. Example stack trace below:

  Traceback (most recent call last):
  File "/usr/src/app/metrics_results_writer/message_schema.py", line 43, in _extract_content
    timestamp = arrow.get(string_timestamp)
  File "/usr/local/lib/python2.7/site-packages/arrow/api.py", line 22, in get
    return _factory.get(*args, **kwargs)
  File "/usr/local/lib/python2.7/site-packages/arrow/factory.py", line 174, in get
    dt = parser.DateTimeParser(locale).parse_iso(arg)
  File "/usr/local/lib/python2.7/site-packages/arrow/parser.py", line 119, in parse_iso
    return self._parse_multiformat(string, formats)
  File "/usr/local/lib/python2.7/site-packages/arrow/parser.py", line 277, in _parse_multiformat
    _datetime = self.parse(string, fmt)
  File "/usr/local/lib/python2.7/site-packages/arrow/parser.py", line 182, in parse
    self._parse_token(token, value, parts)
  File "/usr/local/lib/python2.7/site-packages/arrow/parser.py", line 235, in _parse_token
    parts['tzinfo'] = TzinfoParser.parse(value)
  File "/usr/local/lib/python2.7/site-packages/arrow/parser.py", line 336, in parse
    tzinfo = tz.tzoffset(None, seconds)
  File "/usr/local/lib/python2.7/site-packages/dateutil/tz/_factories.py", line 40, in __call__
    cls.__strong_cache[key] = cls.__strong_cache.pop(key, instance)
  File "/usr/local/lib/python2.7/collections.py", line 157, in pop
    del self[key]
  File "/usr/local/lib/python2.7/collections.py", line 86, in __delitem__
    link_prev, link_next, _ = self.__map.pop(key)
KeyError: (None, 0)

Unfortunately since arrow is depending on dateutil's tz here, there isn't much we can do on our end.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

liosha picture liosha  路  7Comments

Kirkman picture Kirkman  路  5Comments

LaurensBosscher picture LaurensBosscher  路  4Comments

rotten picture rotten  路  4Comments

drozzy picture drozzy  路  8Comments