From e89c654b3aa8671ddd36da0c8404071abe276ec4 Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Tue, 24 Sep 2024 16:17:12 -0700 Subject: [PATCH] Allow stale values requests to be repeated This addresses issue #154, using the same technique as PR #127. --- flaskr/nscope/models.py | 8 +++---- flaskr/nscope/views.py | 53 ++++++++++++++++++++++++++++++++++------- 2 files changed, 48 insertions(+), 13 deletions(-) diff --git a/flaskr/nscope/models.py b/flaskr/nscope/models.py index fee9205..3ae5384 100644 --- a/flaskr/nscope/models.py +++ b/flaskr/nscope/models.py @@ -37,13 +37,13 @@ class Sequence(db.Model): # Postgres reserved word, so we use a different name. shift = db.Column(db.Integer, unique=False, nullable=False, default=0) values = db.Column(db.ARRAY(db.String), unique=False, nullable=True) - values_requested = db.Column(db.Boolean, nullable=False, default=False) raw_refs = db.Column(db.String, unique=False, nullable=True) backrefs = db.Column(db.ARRAY(db.String), unique=False, nullable=True) ref_count = db.Column(db.Integer, nullable=True, default=None) - # The start time of the last attempt to fetch metadata, in nanoseconds since - # the UNIX epoch. A PostgreSQL BigInteger is eight bytes, including sign, so - # this should work until the early 2260s + # The start times of the last attempt to fetch values and metadata, in + # nanoseconds since the UNIX epoch. A PostgreSQL BigInteger is eight bytes, + # including sign, so this should work until the early 2260s + values_req_time = db.Column(db.BigInteger, nullable=True, default=None) meta_req_time = db.Column(db.BigInteger, nullable=True, default=None) # Sadly, multidimensional arrays can't vary in dimension # so we store factorization arrays as strings diff --git a/flaskr/nscope/views.py b/flaskr/nscope/views.py index 0bfcbd3..572462f 100644 --- a/flaskr/nscope/views.py +++ b/flaskr/nscope/views.py @@ -235,16 +235,38 @@ def fetch_values(oeis_id): OEIS (if it has not already been), and returns a Sequence object with the values filled in. """ - # First check if it is in the database seq = find_oeis_sequence(oeis_id) - # See if we already have the values: - if seq.values is not None: return seq - # See if getting them is in progress: - if seq.values_requested: - return LookupError("Value fetching for {oeis_id} in progress.") - seq.values_requested = True + if seq.values is not None: + # We already have the values in the database, so we just return them + return seq + + our_req_time = time.time_ns() + last_req_time = seq.values_req_time + if last_req_time is not None: + # we chose `max_wait = 3` pretty haphazardly. it matches the minimum + # `max_wait` for a metadata request that has some refs downloaded + # already, and it's longer than the time it took to download the A000521 + # b-file on Glen's connection (two seconds for 4 MB) + waited = (our_req_time - last_req_time) / 1e9 + max_wait = 3 + if waited < max_wait: + return LookupError( + f"Values for {oeis_id} were already requested {waited:.1f} " + "seconds ago. A new request can be made if the old one takes " + f"longer than {max_wait:.1f} seconds." + ) + + #--------------------------------------------------------------------------- + # if we've gotten this far, we don't have the values in the database yet, + # and we don't think any other thread is likely to come back with them + #--------------------------------------------------------------------------- + + # Record the time we set out to fetch the values, so later threads can + # judge how likely we are to ever come back + seq.values_req_time = our_req_time db.session.commit() - # Now try to get it from the OEIS: + + # Try to get the b-file from the OEIS: b_text = oeis_get(f'/{oeis_id}/b{oeis_id[1:]}.txt', json=False) # Test for 404 error. Hat tip StackOverflow user Lukasa # https://stackoverflow.com/a/19343099 @@ -283,7 +305,20 @@ def fetch_values(oeis_id): if not seq.name: seq.name = name or placeholder_name(oeis_id) seq.shift = first - db.session.commit() + + # We write what we've found to the database in the following situations: + # + # - No more recent thread has set out to fetch the same metadata + # + # - A more recent thread has set out to fetch the same metadata, but we got + # back before any other thread did + # + # This is equivalent to the condition in the `if` statement below because + # the only way for `seq.values_req_time == our_req_time` to be false is for + # a more recent thread to have overwritten the request time + if seq.values_req_time == our_req_time or seq.values is None: + db.session.commit() + return seq def fetch_factors(oeis_id, num_elements = -1, timeout = 10):