Skip to content

Add onSync callback for implementing action queues - #52

Merged
ai merged 18 commits into
logux:nextfrom
VladBrok:feat/54
Sep 10, 2023
Merged

ai merged 18 commits into
logux:nextfrom
VladBrok:feat/54

Conversation

@VladBrok

@VladBrok VladBrok commented Aug 4, 2023 •

Copy link
Copy Markdown
Contributor

Related to logux/logux#54

This PR adds "onSync" callback that can be used in @logux/server to implement action queues.

Comment thread sync/index.js Outdated
})
let process = processAction.bind(this)
if (this.options.onActions) {
this.options.onActions(process, action, meta)

@ai ai Aug 22, 2023 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

sendSynced will be sent just after onActions call. Don’t we need to do await or something?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Also, we need to be ready for errors in onActions.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

onActions is not async so I didn't add an await

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

То есть ты хочешь переделать протокол и отправлять synced не после inMap, inFilter и onAction (как сейчас) а сразу?

Почему такое изменение хочешь? Какие сайд-эффекты?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Внутри inFilter происходит вызов коллбека access, и я подумал, что его лучше тоже вызывать в очереди, чтобы было так:

  1. выполняется action_1.access
  2. выполняется action_1.process
  3. выполняется action_2.access
  4. выполняется action_2.process

Про изменение протокола не подумал. Может, лучше сделать, что onActions будет вызываться вместо добавления action в лог? То есть сервер будет на onActions добавлять action в лог в порядке очереди, а inMap и inFilter будут вызываться как сейчас

Comment thread sync/index.test.ts Outdated
equal(pair.rightNode.log.actions(), [{ type: 'a1' }])
})

test('calls onActions if present', async () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don’t see how onActions works in a test. I am not sure that we are property testing it.

Comment thread base-node/index.d.ts Outdated
inMap?: LogMapper

/**
* Function that will be called before node sends 'synced'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It is unclear how to use it and why. We need to say a little more details here.

@ai

ai commented Aug 22, 2023

Copy link
Copy Markdown
Member

Can you explain me this API? I didn’t get what it ignoreDestroying and how to use onActions.

I tried to look at Logux Server code, but it didn’t help any Logux Core changes should be self-explained.

You can switch to Russian if it will be simpler.

@VladBrok

Copy link
Copy Markdown
Contributor Author

Can you explain me this API? I didn’t get what it ignoreDestroying and how to use onActions.

I tried to look at Logux Server code, but it didn’t help any Logux Core changes should be self-explained.

You can switch to Russian if it will be simpler.

Спасибо за ревью.

На данный момент, для каждого action, вызывается inMap, затем inFilter, затем action добавляется в лог. При добавлении action в лог, сервер начинает его обрабатывать.

API, которое я добавил, позволяет вызывать inMap, inFilter и добавление action в лог тогда, когда будет нужно. В нашем случае, это будет происходить в очереди.

То есть:

  1. Вызывается onActions(process, action, meta); функция process содержит вызовы inMap, inFilter и добавление action в лог
  2. В onActions, сервер добавляет process, action и meta в очередь.
  3. Очередь вызывает process(action, meta), ждет пока action обработается (on('processed') или on('error')), и после этого переходит к следующему action.

ignoreDestroying нужен для того, чтобы завершить выполнение всех action в очереди перед выключением сервера. Используется здесь: https://github.com/logux/server/pull/146/files#diff-dcb9d56556bf3885bd0d0fd9d88b318f0bdeddd59f85fe50c319ae959bfebd85R230. Без него может получится так:

  1. В очереди 2 action, первый обрабатывается
  2. Сервер начал выключаться
  3. Первый action обработался, очередь переходит ко второму
  4. При добавлении второго action в лог, он не обработается, т.к. стоит проверка if (this.destroying) { return }

@ai

ai commented Aug 23, 2023

Copy link
Copy Markdown
Member

Вызывается onActions(process, action, meta); функция process содержит вызовы inMap, inFilter и добавление action в лог

Всё равно не понимаю API. Дай примеры на псевдо-коде как её использовать (абстрактно, вне сервера).

ignoreDestroying нужен для того, чтобы завершить выполнение всех action в очереди перед выключением сервера.

А мы можем избежать вставки ignoreDestroying? Например, изменить meta через какой-то другой API?

@VladBrok

Copy link
Copy Markdown
Contributor Author

Вызывается onActions(process, action, meta); функция process содержит вызовы inMap, inFilter и добавление action в лог

Всё равно не понимаю API. Дай примеры на псевдо-коде как её использовать (абстрактно, вне сервера).

ignoreDestroying нужен для того, чтобы завершить выполнение всех action в очереди перед выключением сервера.

А мы можем избежать вставки ignoreDestroying? Например, изменить meta через какой-то другой API?

Примеры использования:

onActions(process, action, meta) {
  myActionQueue.schedule(async () => {
    await process(action, meta)
  })
}
```ts
onActions(process, action, meta) {
  myActions.push({process, action, meta})
  if (myActions.length === 10) {
     myActions.forEach(async ({process, action, meta}) => {
       await process(action, meta)
    })
    myActions.clear()
  }
}

@ai

ai commented Aug 23, 2023

Copy link
Copy Markdown
Member

И как эти примеры будут работать? Объясни когда вызовутся inMap, когда сработает колбэк и т. п.

@ai

ai commented Aug 23, 2023

Copy link
Copy Markdown
Member

И объясни почему в API там process() а не, например, возвращение [action, meta].

@VladBrok

Copy link
Copy Markdown
Contributor Author

Добавил комментарии:

onActions(process, action, meta) {
  // Вызываем process(action, meta) не сразу, а сохраняем в очередь
  // myActionQueue будет вызывать коллбеки по очереди, а не параллельно
  myActionQueue.schedule(async () => {
    await process(action, meta) // Вызовет inMap, inFilter и добавить action в лог
  })
}
onActions(process, action, meta) {
  // Вызываем process(action, meta) не сразу, а когда накопиться 10 action'ов
  myActions.push({process, action, meta})
  if (myActions.length === 10) {
     myActions.forEach(async ({process, action, meta}) => {
       await process(action, meta) // Вызовет inMap, inFilter и добавить action в лог 
    })
    myActions.clear()
  }
}

В API там process, потому что внутри него вызываются InMap, inFilter и происходит добавление action в лог. Получается что нужно вызвать process(action, meta) и он вызовет inMap и inFilter.

@ai

ai commented Aug 23, 2023

Copy link
Copy Markdown
Member

А почему нельзя вернуть Промис и вызвать inMap и inFilter в Promise.then()?

@ai

ai commented Aug 23, 2023

Copy link
Copy Markdown
Member

И в чём тогда разница с inMap?

@VladBrok

Copy link
Copy Markdown
Contributor Author

Поменял, чтобы inMap и inFilter вызывались, как сейчас, а onActions будет вызываться вместо добавления action в log (сервер сам будет добавлять action в лог, но уже в очереди, а не сразу).
Поменял API, вот пример:

  onActions(action, meta) {
    // Add action to the log later
    myActionQueue.schedule(() => {
      actionLog.add(action, meta)
    })
  }

Comment thread base-node/index.d.ts Outdated
/**
* Function that will be called instead of adding action to the log
* after inMap and inFilter have been called.
* Use it if you want more control over when an action will be added to the log

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
* Use it if you want more control over when an action will be added to the log
*
* Use it if you want more control over when an action will be added to the log.

Comment thread base-node/index.d.ts Outdated
* Function that will be called instead of adding action to the log
* after inMap and inFilter have been called.
* Use it if you want more control over when an action will be added to the log
* @example

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The project uses different syntax for examples in TypeDoc

Comment thread sync/index.js Outdated
Comment thread base-node/index.d.ts Outdated
Comment thread base-node/index.d.ts
Comment thread base-node/index.d.ts Outdated
* }
* ```
*/
onActions?: ActionsCallback

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

There are two problems with naming:

  1. onActions has s on the end, but we call it for single action.
  2. log.add() also add “action”, why we are not calling onAction there?

What do you think about onSync?

Comment thread sync/index.test.ts Outdated
equal(catched, [error])
})

test('actions are added to the log if onActions is present and if actions were added to the log inside it', async () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Too long line. Let’s name shorter to avoid lines > 80 symbols.

Comment thread sync/index.test.ts Outdated
test('actions are added to the log if onActions is present and if actions were added to the log inside it', async () => {
let pair = await createTest(created => {
created.rightNode.options.inFilter = async (action, meta) => {
type(meta.id, 'string')

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You can remove this checks since we are not testing inFiler and inMap

Comment thread sync/index.test.ts Outdated
return [{ type: action.type + '1' }, meta]
}
created.rightNode.options.onActions = (action, meta) => {
created.rightNode.log.add(action, meta)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This test will not find that onActions is not calling (because log.add is default behavior)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

For instance, let’s put actions to an array and then check the array that it contains actions and log is empty.

Comment thread sync/index.test.ts Outdated
equal(pair.rightNode.log.actions(), [{ type: 'a1' }, { type: 'b1' }])
})

test('actions are not added to the log if onActions is present and if actions were not added to the log inside it', async () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We don’t need this test if we will check array filling in test above

VladBrok and others added 2 commits August 29, 2023 23:47
Co-authored-by: Andrey Sitnik <andrey@sitnik.ru>
Co-authored-by: Andrey Sitnik <andrey@sitnik.ru>
@ai
ai changed the base branch from main to next August 29, 2023 20:58
@VladBrok VladBrok changed the title Add onActions callback for implementing action queues Add onSync callback for implementing action queues Aug 29, 2023
Comment thread sync/index.test.ts Outdated
test('onSync is called instead of adding an action to the log', async () => {
let actions = []
let pair = await createTest(created => {
created.rightNode.options.onSync = (action, meta) => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I mean let’s remove type(action, 'object') from inMap/inFilter.

The inMap/inFilter were useful to test that we call onSync after them.

@VladBrok

Copy link
Copy Markdown
Contributor Author

I've changed an API in order to support calling access in a queue.
onSync now has additional parameter called processAction.
processAction is an async function that calls inMap, inFilter, and Log#add. Server will call it in a queue.
Example:

onSync(processAction, action, meta) {
 // Process an action later
 myActionQueue.schedule(async () => {
   await processAction(action, meta) // will call `inMap`, `inFilter` and `Log#add`
 })
}

Comment thread base-node/index.d.ts Outdated
* }
* ```
*/
onSync?: SyncCallback

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I have plan later to replace inFiler/inMap with onSync (are you agree that it is possibe?)

Can you rename onSync to onReceive (so later we will be able to add onSend for outFilter/outMap)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes this should be possible

@ai
ai merged commit 8cb9d03 into logux:next Sep 10, 2023
ai added a commit that referenced this pull request Mar 11, 2024
* add onActions callback for implementing action queues

* add ignoreDestroying to meta in order to support waiting for actions to finish in queue before destroy

* add test for ignoreDestroying, export ActionsCallback type

* handle error in onActions

* improve onActions description

* add 1 more test for onActions

* remove ignoreDestroying, move related logic to logux server

* use different code example for onActions

* fix typo

* call inMap and inFilter outside of onActions

* review docs issue

* Update base-node/index.d.ts

Co-authored-by: Andrey Sitnik <andrey@sitnik.ru>

* Update base-node/index.d.ts

Co-authored-by: Andrey Sitnik <andrey@sitnik.ru>

* rename onActions to onSync, improve onSync tests

* add inMap and inFilter to the onSync test

* change onSync api to support calling access inside of queue

* rename onSync to onReceive

---------

Co-authored-by: Andrey Sitnik <andrey@sitnik.ru>
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.

2 participants