Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

[Semaphore] Added the component#35780

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
fabpot merged 1 commit intosymfony:masterfromlyrixx:semaphore
Aug 27, 2020
Merged

Conversation

@lyrixx
Copy link
Member

@lyrixxlyrixx commentedFeb 18, 2020
edited
Loading

QA
Branch?yes
Bug fix?no
New feature?yes
Deprecations?no
Tickets
LicenseMIT
Doc PR

[Semaphore] Added the component

Few years ago, we have introduced the Lock component. This is a very nice component, but sometime it is not enough. Sometime you need semaphore.

This is why I'm introducing this new component.

What is a Semaphore ?

From wikipedia:

In computer science, a semaphore is a variable or abstract data type used to control access to a common resource by multiple processes in a concurrent system such as a multitasking operating system. A semaphore is simply a variable. This variable is used to solve critical section problems and to achieve process synchronization in the multi processing environment. A trivial semaphore is a plain variable that is changed (for example, incremented or decremented, or toggled) depending on programmer-defined conditions.

This new component is more than a variable. This is an abstraction on top of different storage.

To make a quick comparison with a lock:

  • A lock allows only 1 process to access a resource;
  • A semaphore allow N process to access a resource.

Basically, a lock is a semaphore whereN = 1.

Possible confusion

PHP exposes somesem_* functions likesem_acquire. This module provides wrappers for the System V IPC family of functions. It includes semaphores, shared memory and inter-process messaging (IPC).

The Lock component has a storage that works with theses functions. It uses it withN = 1.

What are the use-cases ?

Wikipedia has someexamples

But I can add one more commun use case.

If you are building an async system that process user data, you may want to priorise all jobs. You can achieve that by running at maximum N jobs per user at the same time. If the user has more resources, you give him more concurrent jobs (so a biggerN).

Thanks to semaphores, it's pretty easy to know if a new job can be run.

Some concrete use-cases

I'm not saying the following services are using semaphore, but they may solve the previous problematic with semaphores. Here is some examples:

  • services like testing platform where a user can test N projects concurrently (travis, circle, appveyor, insight, ...)
  • services that ingest lots of data (newrelic, datadog, blackfire, segment.io, ...))
  • services that send email in batch (campaign monitor, mailchimp, ...)
  • etc...

How to use it ?

To do so, since PHP is mono-threaded, you run M PHP workers. And in each worker, you look for for the next job. When you grab a job, you try to acquires a semaphore. If you got it, you process the job. If not you try another job.

FTR in other language, like Go, there are no need to run M workers, one is enough.

With Symfony

<?phpuseSymfony\Component\Lock\LockFactory;useSymfony\Component\Lock\Store\RedisStoreasLockRedisStore;useSymfony\Component\Semaphore\SemaphoreFactory;useSymfony\Component\Semaphore\Store\RedisStore;require__DIR__.'/vendor/autoload.php';$redis =newRedis();$redis->connect('172.17.0.2');// Internally, Semaphore needs a lock$lock = (newLockFactory(newLockRedisStore($redis)))->createLock('test:lock',1);// Create a semaphore:// * name = test// * limit = 3 (it means only 3 process are allowed)// * ttl = 10 seconds : Maximum expected semaphore duration in seconds$semaphore = (newSemaphoreFactory($lock,newRedisStore($redis)))->createSemaphore('test',3,10);if (!$semaphore->acquire()) {echo"Could not acquire the semaphore\n";exit(1);}// The semaphore has been acquired// Do the heavy jobfor ($i =0;$i <100; ++$i) {sleep(1);// Before the expiration, refresh the semaphore if the job is not finished yetif ($i %9 ===0) {$semaphore->refresh();    }}// Release it when finished$semaphore->release();

Prior art

I looked atpackagist and:

  • most of packages are using a semaphore storage for creating a lock. So there are not relevant here;
  • some packages need an async framework to be used (amphp for example);
  • the only packages really implementing a semaphore, has a really low code quality and some bugs.

Current implementation

  1. I initially copied the Lock component since the external API is quite similar;
  2. I simplified it a lot for the current use case;
  3. I implemented the RedisStorage according theredis book
  4. I forced a TTL on the storage.

TODO:

  • documentation
  • test
  • move the lock requirements to the redis storage only ? Not needed anymore

GromNaN, Korbeil, ternel, fbourigault, lyrixx, Gompali, Mediagone, Guikingone, juuuuuu, sstok, and 11 more reacted with thumbs up emojiKorbeil, Guikingone, sstok, jeremyFreeAgent, ismail1432, lyrixx, romainnorberg, caillioux, and andreybolonin reacted with hooray emojiismail1432, lyrixx, caillioux, and andreybolonin reacted with rocket emoji
Copy link
Contributor

@OskarStarkOskarStark left a comment

Choose a reason for hiding this comment

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

Nice 👍🏻 I couldn’t say much about the code itself but it should be experimental in the beginning 🙂

lyrixx and sstok reacted with thumbs up emoji
@GromNaN
Copy link
Member

Very useful feature for distributed systems when you need to limit concurrency. We usedRabbitMQ to store semaphores.

lyrixx, damienalexandre, and sstok reacted with heart emoji

@fbourigault
Copy link
Contributor

fbourigault commentedFeb 19, 2020
edited
Loading

This is exactly what I need limit outgoing http connections for a given client.

sstok reacted with hooray emoji

@nicolas-grekasnicolas-grekas added this to thenext milestoneFeb 20, 2020
@jderusse
Copy link
Member

Great idea! I like it.

Wouldn't it make sens to merge it with Lock Component?

I've also an enhancement on my todo list, about adding Read/WriteLock (wellknown as SharedLock for flock) to the Lock Compoenent (basicaly being able to get N (infinity) locks for reads, but 1 lock for Write). And sounds like something realy close to this new SemaphoreComponent

Taluu, Lyrkan, B-Galati, lucascourot, and dkarlovi reacted with thumbs up emojisstok, B-Galati, and skigun reacted with heart emojiLiLinen reacted with rocket emoji

Copy link
Member

@jderussejderusse left a comment

Choose a reason for hiding this comment

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

Nice work. With an effective use of Redis' structs.

What's about High Availability?

@lyrixx
Copy link
MemberAuthor

lyrixx commentedMar 3, 2020
edited
Loading

Wouldn't it make sens to merge it with Lock Component?

I don't think so. Even if theses 2 components look similar, they are not. They do not share code (except the Key and StoreFactory Classes, but they are different).

Semaphore does not require lock anymore.

Again, Theses concept are similar, but they are not the same. That's why they have two different name.

PHP, Linux, Go, Rust have different packages for theses 2 purposes. I think we should stay consistant with others big players.

More over it will be:

  • better for discoverability
  • easier to maintain (I guess)
  • easier to get more accurate stats utilisation

What's about High Availability?

I don't see the issue here. Could you be more specific?

lyrixx and pyrech reacted with thumbs up emoji

stof
stof previously requested changesMar 3, 2020
Copy link
Member

@stofstof left a comment

Choose a reason for hiding this comment

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

Thereplace rule is missing in the rootcomposer.json file.

lyrixx reacted with thumbs up emoji
@bwoebi
Copy link

@lyrixx amphp/sync is relying on amp promises. It does not need the loop though...

@lyrixxlyrixxforce-pushed thesemaphore branch 2 times, most recently from14e6f37 to68e21aaCompareMarch 19, 2020 17:29
@lyrixx
Copy link
MemberAuthor

I changed a bit the implementation. Now it's not as in the Book. But since I'm using a lua script, I'm sure there is no race condition possible (I tested it).
More over, it's not possible to get an "unfair" semaphore.

The system is now simpler and more powerful.

@lyrixxlyrixx requested a review fromjderusseMarch 19, 2020 21:53
Copy link
Member

@jderussejderusse left a comment

Choose a reason for hiding this comment

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

LGTM.

Just a question: In Lock Component we provided a "CombinedStore" in order to provide HighAvailability and guarentee reliability even when a Redis Server in unreachable. Doe you think it worth it?

@lyrixx
Copy link
MemberAuthor

Just a question: In Lock Component we provided a "CombinedStore" in order to provide HighAvailability and guarentee reliability even when a Redis Server in unreachable. Doe you think it worth it?

I don't know. ATM we only have Redis so I don't think this is useful. Maybe later ?

@fabpot
Copy link
Member

@lyrixx@jderusse What's the status here?

@lyrixx
Copy link
MemberAuthor

To me it was ready month ago

fabpot
fabpot previously requested changesAug 11, 2020
Copy link
Member

@fabpotfabpot left a comment

Choose a reason for hiding this comment

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

It looks like the code in the PR description is not up to date anymore, can you update it and provide some code that matches with the current implementation?

Also, as the Redis store had some changes recently, would it make sense to backport some of them here?

@fabpot
Copy link
Member

fabpot commentedAug 18, 2020
edited by chalasr
Loading

@lyrixx Can you have a look at the feedback?

lyrixx reacted with thumbs up emoji

@lyrixxlyrixxforce-pushed thesemaphore branch 2 times, most recently from6575696 to6b7d0f4CompareAugust 27, 2020 10:13
@lyrixx
Copy link
MemberAuthor

@jderusse@fabpot I have addressed your comments. I hope it's Okay now :)

@lyrixxlyrixx requested a review fromfabpotAugust 27, 2020 10:21
@lyrixxlyrixx requested a review fromjderusseAugust 27, 2020 10:21
@lyrixxlyrixx dismissed stale reviews fromstof andfabpotAugust 27, 2020 10:22

Code has been updated

@lyrixx
Copy link
MemberAuthor

Note: The CI is broken, I'm trying to fix it.

Copy link
Member

@jderussejderusse left a comment

Choose a reason for hiding this comment

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

Look good to me, plus few minor comments

Few years ago, we have introduced the Lock component. This is a very nice component, but sometime it is not enough. Sometime you need semaphore.This is why I'm introducing this new component.From wikipedia:> In computer science, a semaphore is a variable or abstract data type used to control access to a common resource by multiple processes in a concurrent system such as a multitasking operating system. A semaphore is simply a variable. This variable is used to solve critical section problems and to achieve process synchronization in the multi processing environment. A trivial semaphore is a plain variable that is changed (for example, incremented or decremented, or toggled) depending on programmer-defined conditions.This new component is more than a variable. This is an abstraction on top of different storage.To make a quick comparison with a lock: * A lock allows only 1 process to access a resource; * A semaphore allow N process to access a resource.Basically, a lock is a semaphore where `N = 1`.PHP exposes some `sem_*` functions like [`sem_acquire`](http://php.net/sem_acquire). This module provides wrappers for the System V IPC family of functions. It includes semaphores, shared memory and inter-process messaging (IPC).The Lock component has a storage that works with theses functions. It uses it with `N = 1`.Wikipedia has some [examples](https://en.wikipedia.org/wiki/Semaphore_(programming)#Examples)But I can add one more commun use case.If you are building an async system that process user data, you may want to priorise all jobs. You can achieve that by running at maximum N jobs per user at the same time. If the user has more resources, you give him more concurrent jobs (so a bigger `N`).Thanks to semaphores, it's pretty easy to know if a new job can be run.I'm not saying the following services are using semaphore, but they may solve the previous problematic with semaphores. Here is some examples: * services like testing platform where a user can test N projects concurrently (travis, circle, appveyor, insight, ...) * services that ingest lots of data (newrelic, datadog, blackfire, segment.io, ...)) * services that send email in batch (campaign monitor, mailchimp, ...) * etc...To do so, since PHP is mono-threaded, you run M PHP workers. And in each worker, you look for for the next job. When you grab a job, you try to acquires a semaphore. If you got it, you process the job. If not you try another job.FTR in other language, like Go, there are no need to run M workers, one is enough.```php<?phpuse Symfony\Component\Lock\LockFactory;use Symfony\Component\Lock\Store\RedisStore as LockRedisStore;use Symfony\Component\Semaphore\SemaphoreFactory;use Symfony\Component\Semaphore\Store\RedisStore;require __DIR__.'/vendor/autoload.php';$redis = new Redis();$redis->connect('172.17.0.2');// Internally, Semaphore needs a lock$lock = (new LockFactory(new LockRedisStore($redis)))->createLock('test:lock', 1);// Create a semaphore:// * name = test// * limit = 3 (it means only 3 process are allowed)// * ttl = 10 seconds : Maximum expected semaphore duration in seconds$semaphore = (new SemaphoreFactory($lock, new RedisStore($redis)))->createSemaphore('test', 3, 10);if (!$semaphore->acquire()) {    echo "Could not acquire the semaphore\n";    exit(1);}// The semaphore has been acquired// Do the heavy jobfor ($i = 0; $i < 100; ++$i) {    sleep(1);    // Before the expiration, refresh the semaphore if the job is not finished yet    if ($i % 9 === 0) {        $semaphore->refresh();    }}// Release it when finished$semaphore->release();```I looked at [packagist](https://packagist.org/?query=semaphore) and: * most of packages are using a semaphore storage for creating a lock. So there are not relevant here; * some packages need an async framework to be used (amphp for example); * the only packages really implementing a semaphore, has a really low code quality and some bugs.1. I initially copied the Lock component since the external API is quite similar;1. I simplified it a lot for the current use case;1. I implemented the RedisStorage according the [redis book](https://redislabs.com/ebook/part-2-core-concepts/chapter-6-application-components-in-redis/6-3-counting-semaphores/;)1. I forced a TTL on the storage.
@lyrixx
Copy link
MemberAuthor

Semaphore component is now 💚 in the CI

@fabpot
Copy link
Member

Thank you@lyrixx.

@fabpotfabpot merged commitce8b497 intosymfony:masterAug 27, 2020
@lyrixxlyrixx deleted the semaphore branchAugust 27, 2020 14:45
@nicolas-grekasnicolas-grekas modified the milestones:next,5.2Oct 5, 2020
@fabpotfabpot mentioned this pull requestOct 5, 2020
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

@OskarStarkOskarStarkOskarStark left review comments

@fabpotfabpotfabpot approved these changes

@jderussejderussejderusse approved these changes

@stofstofAwaiting requested review from stof

+3 more reviewers

@joelwurtzjoelwurtzjoelwurtz left review comments

@fbourigaultfbourigaultfbourigault left review comments

@ismail1432ismail1432ismail1432 left review comments

Reviewers whose approvals may not affect merge requirements

Assignees

No one assigned

Projects

None yet

Milestone

5.2

Development

Successfully merging this pull request may close these issues.

12 participants

@lyrixx@GromNaN@fbourigault@jderusse@bwoebi@fabpot@joelwurtz@stof@OskarStark@ismail1432@nicolas-grekas@carsonbot

[8]ページ先頭

©2009-2025 Movatter.jp