mike-marcacci / node-redlock

A node.js redlock implementation for distributed, highly-available redis locks
MIT License
1.84k stars 172 forks source link

Read/Write lock? #72

Open Menci opened 4 years ago

Menci commented 4 years ago

I'm trying to implement a Read/Write lock, which allows multiple readers or one writer to acquire the lock. But Redlock has no way to unlock something with the resource ID, which is required by the Read/Write lock algorithm. So are there anyway to have a Read/Write lock?

mike-marcacci commented 4 years ago

Hi @Menci,

It's essential to the safety of the locking algorithm that the internal value is present when releasing an acquired lock.

I'm curious why your algorithm would need this ability, if you can explain it here I may be able to point you in another direction.

Menci commented 4 years ago

The algorithm is here https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock#Using_two_mutexes.

The last finished reader should unlock the write lock, but the lock may be locked by another reader.

Menci commented 4 years ago

I can only implement it with the second algorithm in https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock#Using_a_condition_variable_and_a_mutex.

But in Redis I have to reaplce conditional variables with sleeping and polling (https://github.com/syzoj/syzoj-ng/blob/master/src/redis/redis.service.ts#L94). I'm wondering if there're a better approach?

meredian commented 4 years ago

@Menci It's somewhat stale, but still will answer (got interested into topic).

TL;DR: - You can't implement safe R/W Lock on top of only Redlock algorithm, since it requires atomic counter next to lock.

Redstone provides mutex locking only, no counting. Sleeping & polling is unsafe for atomic locking as well - value could be changed by neighbour request before you know about it. Distributed systems 🤷 Everything what's not atomic inside single data source is unsafe - intermediate state could be altered by some concurrent interaction from different request/thread.

You could design something similar on your own, but then you need to dive into internals and write your own "EVAL" version of REDIS script to atomically lock keys & increment/decrement counters.

Menci commented 4 years ago

@meredian I know simple sleeping & polling is unsafe. I used a redlock to read and write the counters to ensure the consistant of the state.

https://github.com/syzoj/syzoj-ng/blob/804c94afda1929b85a88fca5ba7339f8e9c9d964/src/redis/redis.service.ts#L107

meredian commented 4 years ago

@Menci So, if I got it right you use Redlock to lock counters as a resource, so they could be operated atomically 🙂 That's kind smart, yeah! I thought your original intention was to count using redlock itself (which doesn't work obviously). I think it's best possible solution using library.

Menci commented 4 years ago

Unfortunately there doesn't seem to be a RW lock library with Redis on NPM.

erayhanoglu commented 11 months ago

Implementing an Read/Write lock algorithm would be extremely useful.

taylorthurlow commented 11 months ago

I stumbled in here from a web search trying to solve the same problem but in Ruby land - also trying to implement an algorithm from the aforementioned Wikipedia page. Unless I misunderstand something about how condition variables work, I didn't see an easy way to replicate "thread land" condition variables in the context of Redis and multiple hosts. So I started to implement the read-preferring algorithm on the same page, and realized that while redlock-rb doesn't explicitly provide a mechanism for unlocking a lock from a process that did not originally acquire the lock, I was able to work around that.

At least in the case of redlock-rb (the Ruby implementation, obviously), the method to acquire a lock, when successful, returns a plain Ruby Hash object, containing the actual lock ID. I was able to serialize this and store it in Redis, so when it comes time for the "final reader" do unlock the global lock, it fetches the lock hash from Redis, deserializes it, and passes it to the redlock-rb-provided #unlock method.

I suppose this really depends on implementation, but the Ruby one at least means that I was free to do what I wanted with the lock details, including store them for later.