Skip to content

Commit df3ad29

Browse files
committed
publish
1 parent 260d933 commit df3ad29

19 files changed

+452
-37
lines changed

LICENSE

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2019 Codevor - js-library-boilerplate
3+
Copyright (c) 2019 Codevor - js-semaphore
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

+92-16
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,110 @@
1-
# js-library-boilerplate
1+
# 🚦js-semaphore - Syncronization Using Promises
22

33
[![License][license-badge]][license-url] [![Travis CI][travis-badge]][travis-url] [![Coverage Status][coverage-badge]][coverage-url] [![Commitizen][commitizen-badge]][commitizen-url]
44

5-
> js-library-boilerplate-description.
5+
🚦`js-semaphore` is a simple **Semaphore** implementation using **Promises** for JavaScript aplications.
6+
7+
If you ever find a need for a syncronization mechanism usage into your application, you can use a Semaphore. If you want to use a Semaphore and you develop in JavaScript, you can use `js-semaphore`!
8+
9+
If your need is simple and a small mechanism with just **1 shared resource**, you can use `js-semaphore` as a **Mutex** too!
10+
11+
The source code comprehends the full **Semaphore** implementation with some basics tests and some applications. Actually, we have:
12+
13+
- 🤔🍜 Philosophers Dinner
14+
15+
#### Common questions
16+
17+
- _Why the tests take so long to complete?_
18+
19+
As this is a Semaphore implementation, the tests are based in time and waiting for a specific resource to continue the execution. We emulate this as a test in order to validate the correct behavior of the lib.
20+
21+
- _Why implement a Semaphore? Using Promises?_
22+
23+
As a common solver for deadlocks avoidance, we found an util lib to create and test! The Promises usage is just a matter of asynchronous need.
624

725
## Installation
826

9-
js-library-boilerplate is available on npm/yarn:
27+
`js-semaphore` is available with npm/yarn:
1028

1129
```bash
12-
$ npm install js-library-boilerplate --save
13-
$ yarn add js-library-boilerplate
30+
$ npm install js-semaphore --save
31+
$ yarn add js-semaphore
1432
```
1533

1634
## Usage
1735

1836
### With ES6/import
1937

2038
```js
21-
import { sum } from 'js-library-boilerplate';
39+
import { Semaphore } from 'js-semaphore';
40+
41+
// Semaphore with 1 resource = Mutex
42+
const semaphore = Semaphore();
43+
44+
// Semaphore with 1 resource, starting at 0 value
45+
const semaphore = Semaphore({ resource: 1, start: 0 });
2246

23-
sum(2, 2); // => 4
47+
// Semaphore with 3 resources
48+
const semaphore = Semaphore({ resource: 3 });
49+
50+
// acquire the semaphore
51+
semaphore.acquire().then(() => {
52+
// Your code goes here
53+
const x = 2 + 3;
54+
55+
// remember to release the semaphore at the end of your usage
56+
semaphore.release();
57+
});
2458
```
2559

2660
### With require
2761

2862
```js
29-
const sum = require('js-library-boilerplate').sum;
63+
const Semaphore = require('js-semaphore').Semaphore;
64+
65+
// Semaphore with 1 resource = Mutex
66+
const semaphore = Semaphore();
67+
68+
// Semaphore with 1 resource, starting at 0 value
69+
const semaphore = Semaphore({ resource: 1, start: 0 });
70+
71+
// Semaphore with 3 resources
72+
const semaphore = Semaphore({ resource: 3 });
73+
74+
// acquire the semaphore
75+
semaphore.acquire().then(() => {
76+
// Your code goes here
77+
const x = 2 + 3;
78+
79+
// remember to release the semaphore at the end of your usage
80+
semaphore.release();
81+
});
82+
```
83+
84+
### Control timeout
85+
86+
If you want, you can control the timeout. We **hardly** suggest not let this below `0.2s`, but you can try if you want.
87+
88+
```js
89+
import { Semaphore, setTimespan, timespan } from 'js-semaphore';
90+
91+
// creates the Semaphore
92+
const semaphore = Semaphore();
93+
94+
// get the actual Timespan
95+
const actualTimespan = timespan();
96+
97+
// set the actual Timespan, in seconds
98+
setTimespan(actualTimespan / 2);
99+
100+
// acquire the semaphore
101+
semaphore.acquire().then(() => {
102+
// Your code goes here
103+
const x = 2 + 3;
30104

31-
sum(2, 2); // => 4
105+
// remember to release the semaphore at the end of your usage
106+
semaphore.release();
107+
});
32108
```
33109

34110
## Contributing
@@ -37,21 +113,21 @@ Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduc
37113

38114
## Changelog
39115

40-
This project adheres to [Semantic Versioning](https://semver.org/). Every release, along with the migration instructions, is documented on the Github [Releases](https://github.com/codevor/js-library-boilerplate/releases) page.
116+
This project adheres to [Semantic Versioning](https://semver.org/). Every release, along with the migration instructions, is documented on the Github [Releases](https://github.com/codevor/js-semaphore/releases) page.
41117

42118
## Bugs and Sugestions
43119

44-
Report bugs or do suggestions using the [issues](https://github.com/codevor/js-library-boilerplate/issues).
120+
Report bugs or do suggestions using the [issues](https://github.com/codevor/js-semaphore/issues).
45121

46122
## License
47123

48124
[MIT License](LICENSE) © [Codevor](https://github.com/codevor)
49125

50-
[license-badge]: https://img.shields.io/github/license/codevor/js-library-boilerplate.svg
126+
[license-badge]: https://img.shields.io/github/license/codevor/js-semaphore.svg
51127
[license-url]: https://opensource.org/licenses/MIT
52-
[coverage-badge]: https://coveralls.io/repos/github/codevor/js-library-boilerplate/badge.svg?branch=master
53-
[coverage-url]: https://coveralls.io/github/codevor/js-library-boilerplate?branch=master
54-
[travis-badge]: https://travis-ci.org/codevor/js-library-boilerplate.svg?branch=master
55-
[travis-url]: https://travis-ci.org/codevor/js-library-boilerplate
128+
[coverage-badge]: https://coveralls.io/repos/github/codevor/js-semaphore/badge.svg?branch=master
129+
[coverage-url]: https://coveralls.io/github/codevor/js-semaphore?branch=master
130+
[travis-badge]: https://travis-ci.org/codevor/js-semaphore.svg?branch=master
131+
[travis-url]: https://travis-ci.org/codevor/js-semaphore
56132
[commitizen-badge]: https://img.shields.io/badge/commitizen-friendly-brightgreen.svg
57133
[commitizen-url]: http://commitizen.github.io/cz-cli/

package.json

+14-9
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
{
2-
"name": "js-library-boilerplate",
2+
"name": "js-semaphore",
33
"version": "0.1.0",
4-
"description": "js-library-boilerplate-description",
5-
"main": "dist/js-library-boilerplate.js",
6-
"unpkg": "dist/js-library-boilerplate.min.js",
4+
"description": "🚦Semaphore Implementation for JS",
5+
"main": "dist/js-semaphore.js",
6+
"unpkg": "dist/js-semaphore.min.js",
77
"scripts": {
88
"clean": "rimraf dist",
99
"dev": "NODE_ENV=dev webpack --progress --colors --watch",
@@ -15,17 +15,17 @@
1515
"prepublish": "yarn lint && yarn test && yarn clean && yarn build:umd",
1616
"commit": "git-cz"
1717
},
18-
"keywords": [],
19-
"author": "Helder Burato Berto <helder.burato@gmail.com> (https://helder.dev/)",
18+
"keywords": ["Semaphore", "Syncronization", "Mutex"],
19+
"author": "Ilê Caian <ile.caian@gmail.com> (https://ile.pink/)",
2020
"license": "MIT",
2121
"repository": {
2222
"type": "git",
23-
"url": "git+https://github.com/codevor/js-library-boilerplate.git"
23+
"url": "git+https://github.com/codevor/js-semaphore.git"
2424
},
2525
"bugs": {
26-
"url": "https://github.com/codevor/js-library-boilerplate/issues"
26+
"url": "https://github.com/codevor/js-semaphore/issues"
2727
},
28-
"homepage": "https://github.com/codevor/js-library-boilerplate#readme",
28+
"homepage": "https://github.com/codevor/js-semaphore#readme",
2929
"devDependencies": {
3030
"@babel/cli": "^7.6.4",
3131
"@babel/core": "^7.6.4",
@@ -50,5 +50,10 @@
5050
"commitizen": {
5151
"path": "./node_modules/cz-conventional-changelog"
5252
}
53+
},
54+
"jest": {
55+
"coveragePathIgnorePatterns": [
56+
"<rootDir>/tests/utils/"
57+
]
5358
}
5459
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const SEMAPHORE_TIMESPAN_SECONDS = 0.5;

src/index.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
export function sum(x, y) {
2-
return x + y;
3-
}
1+
export { default as Semaphore } from './semaphore';
2+
export * from './utils/timespan';
3+
export * from './constants/semaphore-timespan-seconds';

src/semaphore.js

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { timespan } from './utils/timespan';
2+
3+
const Semaphore = ({ resources = 1, start = resources } = {}) => {
4+
let counter = start >= resources ? resources : start;
5+
6+
const acquire = () => {
7+
if (counter > 0) {
8+
counter -= 1;
9+
return Promise.resolve();
10+
}
11+
12+
return new Promise(resolve => {
13+
setTimeout(() => {
14+
acquire().then(() => {
15+
resolve();
16+
});
17+
}, timespan() * 1000);
18+
});
19+
};
20+
21+
const release = () => {
22+
if (counter < resources) {
23+
counter += 1;
24+
}
25+
};
26+
27+
return {
28+
acquire,
29+
release
30+
};
31+
};
32+
33+
export default Semaphore;

src/utils/is-valid-number.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
const isValidNumber = value => typeof value === 'number';
2+
3+
export default isValidNumber;

src/utils/timespan.js

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import isValidNumber from './is-valid-number';
2+
import { SEMAPHORE_TIMESPAN_SECONDS } from '../constants/semaphore-timespan-seconds';
3+
4+
let userTimespan;
5+
6+
export const timespan = () =>
7+
isValidNumber(userTimespan) ? userTimespan : SEMAPHORE_TIMESPAN_SECONDS;
8+
9+
export const setTimespan = value => {
10+
if (isValidNumber(value)) {
11+
userTimespan = value;
12+
}
13+
};
+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { Semaphore } from '../../src';
2+
import philosopher from './philosophers-dinner/philosopher';
3+
4+
const ENTITIES_COUNT = 5;
5+
const PHILOSOPHERS_ACTIONS_COUT = 3;
6+
7+
const TEST_TIMEOUT_SECONDS = 20;
8+
9+
jest.setTimeout(TEST_TIMEOUT_SECONDS * 1000);
10+
11+
describe('app/philosophers-dinner', () => {
12+
test('run philosophers dinner', () => {
13+
const entities = Array.from(new Array(ENTITIES_COUNT).keys());
14+
const actionTimes = Array.from(new Array(PHILOSOPHERS_ACTIONS_COUT).keys());
15+
16+
const chopsticks = entities.map(() => {
17+
return Semaphore({ resources: 1, start: 0 });
18+
}, []);
19+
20+
const philosophers = entities.map(entityIndex => {
21+
return philosopher({
22+
index: entityIndex,
23+
entitiesCount: ENTITIES_COUNT,
24+
chopsticks
25+
});
26+
}, []);
27+
28+
const philosophersPromises = actionTimes.reduce(acc => {
29+
return [
30+
...acc,
31+
...philosophers.map(philosopherItem => {
32+
return philosopherItem();
33+
})
34+
];
35+
}, []);
36+
37+
chopsticks.forEach(chopstick => {
38+
chopstick.release();
39+
});
40+
41+
return Promise.all(philosophersPromises).then(data => {
42+
expect(data.length).toEqual(ENTITIES_COUNT * PHILOSOPHERS_ACTIONS_COUT);
43+
44+
data.forEach(value => {
45+
expect(value).toBeGreaterThanOrEqual(1);
46+
});
47+
});
48+
});
49+
});
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
export const eat = ({ actionTimeout, chopsticks }) =>
2+
new Promise(resolve => {
3+
setTimeout(() => {
4+
// release chopsticks
5+
chopsticks.forEach(chopstick => {
6+
chopstick.release();
7+
});
8+
9+
resolve();
10+
}, actionTimeout);
11+
});
12+
13+
export const think = ({ actionTimeout }) =>
14+
new Promise(resolve => {
15+
setTimeout(() => {
16+
resolve();
17+
}, actionTimeout);
18+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export const getChopsticksIndex = ({ index, entitiesCount }) => {
2+
if (index !== entitiesCount - 1) {
3+
return [index, index + 1];
4+
}
5+
return [0, index];
6+
};
7+
8+
export const getChopstick = chopstick => {
9+
return chopstick.acquire();
10+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { getChopsticksIndex, getChopstick } from './chopstick';
2+
import { eat, think } from './actions';
3+
4+
const philosopher = ({ index, entitiesCount, chopsticks }) => () =>
5+
new Promise(resolve => {
6+
const [firstChopstickIndex, secondChopstickIndex] = getChopsticksIndex({
7+
index,
8+
entitiesCount
9+
});
10+
11+
return getChopstick(chopsticks[firstChopstickIndex]).then(() => {
12+
return getChopstick(chopsticks[secondChopstickIndex]).then(() => {
13+
const ACTION_TIMEOUT = 200 + index * 100;
14+
eat({
15+
actionTimeout: ACTION_TIMEOUT,
16+
chopsticks: [
17+
chopsticks[firstChopstickIndex],
18+
chopsticks[secondChopstickIndex]
19+
]
20+
}).then(() => {
21+
think({ actionTimeout: ACTION_TIMEOUT }).then(() => {
22+
resolve(index + 1);
23+
});
24+
});
25+
});
26+
});
27+
});
28+
29+
export default philosopher;

0 commit comments

Comments
 (0)