Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use m̶u̶l̶t̶i̶p̶r̶o̶c̶e̶s̶s̶i̶n̶g̶ asyncio locks instead of db locks #256

Merged
merged 5 commits into from Jun 23, 2023

Conversation

callebtc
Copy link
Collaborator

No description provided.

@callebtc callebtc requested a review from AngusP June 23, 2023 10:23
@codecov
Copy link

codecov bot commented Jun 23, 2023

Codecov Report

Patch coverage: 17.14% and project coverage change: +0.28 🎉

Comparison is base (62a6ec3) 56.03% compared to head (fa2b6dd) 56.32%.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #256      +/-   ##
==========================================
+ Coverage   56.03%   56.32%   +0.28%     
==========================================
  Files          42       42              
  Lines        3346     3320      -26     
==========================================
- Hits         1875     1870       -5     
+ Misses       1471     1450      -21     
Impacted Files Coverage Δ
cashu/wallet/cli/cli.py 49.72% <0.00%> (-0.14%) ⬇️
cashu/mint/ledger.py 34.90% <17.64%> (+2.17%) ⬆️

... and 1 file with indirect coverage changes

☔ View full report in Codecov by Sentry.
📢 Do you have feedback about the report comment? Let us know in this issue.

Comment on lines 658 to 660
self.locks[hash] = (
self.locks.get(hash) or mp.Lock()
) # create a new lock if it doesn't exist
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could make self.locks a defaultdict so it creates for you?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like defaultdict doesn't accept classes by default (hehe) and would need some additional boilerplate: https://stackoverflow.com/questions/32932494/defaultdict-of-a-class-get-index-key-of-current-called-instance-inside-a-class

@@ -25,6 +27,9 @@


class Ledger:
locks: Dict[str, LockBase] = {} # holds multiprocessing locks
proofs_pending_lock: LockBase = mp.Lock() # holds locks for proofs_pending database
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you know if there's precedent for using multiprocessing.Lock with async? Concern here would be the usual that if it's blocking it'll cause similar async problems/thread-lockups to the blocking requests.

There's also asyncio.Lock https://docs.python.org/3/library/asyncio-sync.html

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review! I chose to not use asyncio.Lock (I used it before this PR) because eventually we want to be able to run the nutshell mint on multiple workers and only the multiprocessing lock allows you do lock across different threads. See here: https://stackoverflow.com/questions/18213619/sharing-a-lock-between-gunicorn-workers

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if there is any interference between the two though, I will give it a test and see if the lock actually works.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it does turn out to be blocking, you can always run in an executor https://docs.python.org/3/library/asyncio-dev.html#running-blocking-code -- adds a bit of overhead but would work

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was indeed blocking. Once two threads ran into the lock, the whole thing stops. I reverted to asyncio locks for now but this won't be usable if we use multiprocessing in the future.

@callebtc callebtc changed the title use mp locks instead of db locks Use m̶u̶l̶t̶i̶p̶r̶o̶c̶e̶s̶s̶i̶n̶g̶ asyncio locks instead of db locks Jun 23, 2023
Copy link
Collaborator

@AngusP AngusP left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, assuming you've tested it 😆

async with self.locks[hash]:
async with self.db.connect() as conn:
# will raise an exception if the invoice is not paid
await self._check_lightning_invoice(amount, hash, conn)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should you clean up self.locks once the invoice has been paid? Otherwise it'll start filling up, which is probably OK but unnecessary.

Would probably need to do this with the lock acquired, then remove it from the dict and let the function return clean up the lock itself when it isn't referenced anymore

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I now delete the locks after they're released, I think that should be safe.

@callebtc callebtc merged commit a3e67d2 into main Jun 23, 2023
5 checks passed
@callebtc callebtc deleted the asyncio_locks_instead_of_db branch June 23, 2023 17:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants