Skip to content

Commit

Permalink
fix(h5p-server): fix library install race conditions (#1871)
Browse files Browse the repository at this point in the history
* test(h5p-server): added failing test

* fix(h5p-server): fixed race condition when installing libraries

* fix(h5p-server): prevent deadlocks due to library order

* test(integration): throttled integration tests

* test(integration): changed timeouts

* ci: added ramdisk usage

* fix(h5p-server): increased default library install lock timeouts

* ci: ramdisk fix

* ci: fixed typo

* ci: increased timeout times

* ci: try to fix timeouts

* ci: fix jest call

* ci: even more extreme timeouts

* feat: added Redis lock provider

* test(h5p-redis-lock): added test for redis lock

* test(h5p-redis-lock): improved tests

* test(h5p-redis-lock): use mocks for redis tests

* docs(h5p-redis-lock): added docs
  • Loading branch information
sr258 authored Nov 16, 2021
1 parent 13fb8dd commit 9286cc6
Show file tree
Hide file tree
Showing 39 changed files with 33,501 additions and 31,712 deletions.
67 changes: 39 additions & 28 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
version: 2.1
# orbs:
# coveralls: coveralls/coveralls@1.0.6
orbs:
coveralls: coveralls/coveralls@1.0.6

jobs:
install-build:
docker:
- image: circleci/node:16
working_directory: /mnt/ramdisk
steps:
- checkout
- checkout
- restore_cache:
key: h5p-core-cache1-{{ checksum "scripts/install.sh" }}
- restore_cache:
Expand All @@ -16,22 +17,22 @@ jobs:
- restore_cache:
key: h5p-examples-cache-{{ checksum "test/data/content-type-cache/real-content-types.json" }}
- run: |
npm run build --prefix packages/h5p-rest-example-client
npm run build --prefix packages/h5p-rest-example-server
npm run download:content
npm run build --prefix packages/h5p-rest-example-client
npm run build --prefix packages/h5p-rest-example-server
npm run download:content
- save_cache:
key: h5p-core-cache1-{{ checksum "scripts/install.sh" }}
paths:
- ./packages/h5p-examples/h5p
- /mnt/ramdisk/packages/h5p-examples/h5p
- save_cache:
key: h5p-core-cache2-{{ checksum "scripts/install.sh" }}
paths:
- ./packages/h5p-rest-example-server/h5p
- /mnt/ramdisk/packages/h5p-rest-example-server/h5p
- save_cache:
key: h5p-examples-cache-{{ checksum "test/data/content-type-cache/real-content-types.json" }}
paths:
- ./test/data/hub-content
- /mnt/ramdisk/test/data/hub-content
- persist_to_workspace:
root: .
paths:
Expand Down Expand Up @@ -62,6 +63,7 @@ jobs:
lint:
docker:
- image: circleci/node:16
working_directory: /mnt/ramdisk
steps:
- checkout
- attach_workspace:
Expand All @@ -71,6 +73,7 @@ jobs:
format:
docker:
- image: circleci/node:16
working_directory: /mnt/ramdisk
steps:
- checkout
- attach_workspace:
Expand All @@ -82,43 +85,50 @@ jobs:
- image: circleci/node:16-browsers
environment:
NODE_ENV: development
TMPDIR: /mnt/ramdisk
steps:
- checkout
- attach_workspace:
at: .
- run: npm run test -- --collect-coverage
# - coveralls/upload:
# parallel: true
# flag_name: Unit tests
- run: npx jest --runInBand --collect-coverage
- coveralls/upload:
parallel: true
flag_name: Unit tests

integration-tests:
docker:
- image: circleci/node:16-browsers
environment:
TMPDIR: /mnt/ramdisk
steps:
- checkout
- attach_workspace:
at: .
- run: npm run test:integration -- --collect-coverage
# - coveralls/upload:
# parallel: true
# flag_name: Integration tests
- run: npx jest --config jest.integration.config.js --runInBand --collect-coverage
- coveralls/upload:
parallel: true
flag_name: Integration tests

html-exporter-tests:
docker:
- image: circleci/node:16-browsers
environment:
TMPDIR: /mnt/ramdisk
steps:
- checkout
- attach_workspace:
at: .
- run: npm run test:html-exporter -- --collect-coverage
# - coveralls/upload:
# parallel: true
# path_to_lcov: ./packages/h5p-html-exporter/coverage/lcov.info
# flag_name: HTML Exporter Tests
- run: npx jest --config packages/h5p-html-exporter/jest.config.js --runInBand --collect-coverage
- coveralls/upload:
parallel: true
path_to_lcov: ./packages/h5p-html-exporter/coverage/lcov.info
flag_name: HTML Exporter Tests

e2e-tests:
docker:
- image: circleci/node:16-browsers
environment:
TMPDIR: /mnt/ramdisk
steps:
- checkout
- attach_workspace:
Expand All @@ -142,20 +152,21 @@ jobs:
- attach_workspace:
at: .
- run: npm run test:db -- --collect-coverage
# - coveralls/upload:
# parallel: true
# flag_name: DB tests
- coveralls/upload:
parallel: true
flag_name: DB tests

release:
docker:
- image: 'circleci/node:16'
working_directory: /mnt/ramdisk
steps:
- checkout
- run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc
- attach_workspace:
at: .
# - coveralls/upload:
# parallel_finished: true
- coveralls/upload:
parallel_finished: true
- run:
name: config git
command: git config --global user.email "c@Lumi.education" && git config --global user.name "Lumi"
Expand Down
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ be installed through NPM. The packages are meant to be combined
| **@lumieducation/h5p-webcomponents** | native web components to display the H5P player and editor in the browser | frontend |
| **@lumieducation/h5p-react** | React components with the same functionality as the native web components | frontend |
| **@lumieducation/h5p-mongos3** | storage classes for MongoDB and S3 | backend |
| **@lumieducation/h5p-redis-lock** | storage class for locks with Redis | backend |
| **@lumieducation/h5p-html-exporter** | an optional component that can create bundled HTML files for exporting | backend |

## Examples
Expand Down
6 changes: 3 additions & 3 deletions docs/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* [Integrating the core library](usage/integrating.md)
* [H5P Ajax Endpoints](usage/ajax-endpoints.md)
* [Constructing H5PEditor](usage/h5p-editor-constructor.md)

* [REST Example](examples/rest/README.md)
- Advanced usage
* [Localization](advanced/localization.md)
* [Cluster](advanced/cluster.md)
Expand All @@ -21,10 +21,10 @@
* [Mongo Library Storage](packages/h5p-mongos3/mongo-library-storage.md)
* [Mongo/S3 Library Storage](packages/h5p-mongos3/mongo-s3-library-storage.md)
- [h5p-webcomponents](packages/h5p-webcomponents.md)
- [h5p-react](packages/h5p-react.md)
- [h5p-react](packages/h5p-react.md)
- [h5p-redis-lock](packages/h5p-redis-lock.md)
- Development
* [Getting started](development/getting-started.md)
* [REST Example](examples/rest/README.md)
* [Testing & code quality](development/testing-quality.md)
* [Core updates](development/core-updates.md)
* [Project Status](development/status.md)
10 changes: 7 additions & 3 deletions docs/advanced/cluster.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ You can access the application by calling
## Commands

Change directories to the directory that contains the `docker-compose.yml` file.
(`packages/h5p-examples/cluster-mode`)

### Startup (fires up 4 instances of h5p)

Expand Down Expand Up @@ -53,6 +54,9 @@ services that make @lumieducation/h5p-server work in cluster mode:

- Minio (provides S3 storage backend for content, temporary and library files)
- MongoDB (provides database backend for content and library metadata)
- Redis (provides a key-value cache; used for caching the content type cache and caching library metadata)
- a named Docker volume (added as a volume to all h5p containers to keep library data consistent across instances)
- NGINX (load balancer that distributes incoming request between the H5P containers)
- Redis (provides a key-value cache; used for caching the content type cache and
caching library metadata; used as a locking mechanism across containers)
- a named Docker volume (added as a volume to all h5p containers to keep library
data consistent across instances)
- NGINX (load balancer that distributes incoming request between the H5P
containers)
24 changes: 24 additions & 0 deletions docs/packages/h5p-redis-lock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# h5p-redis-lock

This package provides a lock mechanism that can be used if the library runs in
multi-process or cluster-mode. The locks are needed to avoid race conditions
when installing libraries.

```ts
import ioredis from 'ioredis-mock';
import { H5PEditor, H5PPlayer } from '@lumieducation/h5p-server';
import RedisLockProvider from '@lumieducation/h5p-redis-lock';

// Create a regular ioredis connection
const redis = new ioredis(redisPort, redisHost, { db: redisDb });

// Create the lock provider
const lockProvider = new RedisLockProvider(redis);

// Pass it to the editor and player object
const h5pEditor = new H5PEditor( /*other parameters*/, options: { lockProvider } );
const h5pPlayer = new H5PPlayer( /*other parameters*/, options: { lockProvider } );
```

It is important to make sure that all instances of H5PEditor use a redis lock
provider that points to the same database. Otherwise race conditions can happen.
17 changes: 15 additions & 2 deletions docs/usage/h5p-editor-constructor.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,16 @@

There are two ways of creating a H5PEditor object:

* You can use the convenience function [`H5P.fs(...)`](/packages/h5p-server/src/implementation/fs/index.ts) that uses basic file system implementations for all data storage services. You can use the function if you're just getting started. Later on, you'll want to construct the editor with custom implementations of the data storage services. Check out the JSDoc of the function for details how to use it.
* You can construct it manually by calling `new H5P.H5PEditor(...)`. The constructor arguments are used to provide data storage services and settings. You can find the interfaces referenced in [`src/types.ts`](/packages/h5p-server/src/types.ts).
* You can use the convenience function
[`H5P.fs(...)`](/packages/h5p-server/src/implementation/fs/index.ts) that uses
basic file system implementations for all data storage services. You can use
the function if you're just getting started. Later on, you'll want to
construct the editor with custom implementations of the data storage services.
Check out the JSDoc of the function for details how to use it.
* You can construct it manually by calling `new H5P.H5PEditor(...)`. The
constructor arguments are used to provide data storage services and settings.
You can find the interfaces referenced in
[`src/types.ts`](/packages/h5p-server/src/types.ts).

Explanation of the arguments of the constructor:

Expand Down Expand Up @@ -101,3 +109,8 @@ tokens to POST URLs.

See the third-party [H5PServer](https://github.com/BoBiene/H5PServer) project
for an implementation sample using the urlGenerator.

## options (optional)

Allows you to customize styles and scripts of the client. Also allows passing in
a lock implementation (needed for multi-process or clustered setups).
Loading

0 comments on commit 9286cc6

Please sign in to comment.