Skip to content

Commit

Permalink
Fix race condition in add_feed_tag().
Browse files Browse the repository at this point in the history
For #266.
  • Loading branch information
lemon24 committed Jan 2, 2022
1 parent 3d07bb6 commit 5da910b
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 18 deletions.
61 changes: 50 additions & 11 deletions src/reader/_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@
from typing import Mapping
from typing import NamedTuple
from typing import Optional
from typing import overload
from typing import Sequence
from typing import Tuple
from typing import Type
from typing import TypeVar
from typing import Union

from ._sql_utils import BaseQuery
from ._sql_utils import paginated_query
Expand Down Expand Up @@ -54,6 +56,8 @@
from .types import FeedCounts
from .types import FeedSortOrder
from .types import JSONType
from .types import MISSING
from .types import MissingType

APPLICATION_ID = int(''.join(f'{ord(c):x}' for c in 'read'), 16)

Expand Down Expand Up @@ -150,7 +154,7 @@ def create_feed_tags(db: sqlite3.Connection) -> None:
CREATE TABLE feed_tags (
feed TEXT NOT NULL,
key TEXT NOT NULL,
value TEXT NOT NULL, -- FIXME: should be nullable
value TEXT NOT NULL,
PRIMARY KEY (feed, key),
FOREIGN KEY (feed) REFERENCES feeds(url)
Expand Down Expand Up @@ -1119,19 +1123,54 @@ def row_factory(t: Tuple[Any, ...]) -> Tuple[str, JSONType]:
self.db, query, context, chunk_size, last, row_factory
)

@wrap_exceptions(StorageError)
@overload
def set_tag(self, object_id: Tuple[str, ...], key: str) -> None:
...

@overload
def set_tag(self, object_id: Tuple[str, ...], key: str, value: JSONType) -> None:
...

@wrap_exceptions(StorageError)
def set_tag(
self,
object_id: Tuple[str, ...],
key: str,
value: Union[MissingType, JSONType] = MISSING,
) -> None:
info = SCHEMA_INFO[len(object_id)]

columns = info.id_columns + ('key', 'value')
query = f"""
INSERT OR REPLACE INTO {info.table_prefix}tags (
{', '.join(columns)}
) VALUES (
{', '.join(('?' for _ in columns))}
)
"""
params = object_id + (key, json.dumps(value))
params = dict(zip(info.id_columns, object_id), key=key)

if value is not MISSING:
query = f"""
INSERT OR REPLACE INTO {info.table_prefix}tags (
{', '.join(info.id_columns)}, key, value
) VALUES (
{', '.join((f':{c}' for c in info.id_columns))},
:key,
:value
)
"""
params.update(value=json.dumps(value))

else:
query = f"""
INSERT OR REPLACE INTO {info.table_prefix}tags (
{', '.join(info.id_columns)}, key, value
) VALUES (
{', '.join((f':{c}' for c in info.id_columns))},
:key,
coalesce((
SELECT value FROM {info.table_prefix}tags
WHERE (
{', '.join(info.id_columns)}, key
) == (
{', '.join((f':{c}' for c in info.id_columns))}, :key
)
), 'null')
)
"""

with self.db:
try:
Expand Down
8 changes: 1 addition & 7 deletions src/reader/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -1875,13 +1875,7 @@ def add_feed_tag(self, feed: FeedInput, tag: str) -> None:
"""
feed_url = _feed_argument(feed)

# FIXME: race condition
self.set_feed_metadata_item(
feed_url,
tag,
self.get_feed_metadata_item(feed_url, tag, None), # type: ignore
)
self._storage.set_tag((feed_url,), tag)

def remove_feed_tag(self, feed: FeedInput, tag: str) -> None:
"""Remove a tag from a feed.
Expand Down

0 comments on commit 5da910b

Please sign in to comment.