Skip to content
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MM-48342] Stateful timers #375

Merged
merged 16 commits into from
Feb 21, 2023
Merged

[MM-48342] Stateful timers #375

merged 16 commits into from
Feb 21, 2023

Conversation

hanzei
Copy link
Contributor

@hanzei hanzei commented Oct 28, 2022

Summary

This is a first draft of state full timer. Apps can now create timers by defining a time when the timer should be executed.

The API is:

type Timer struct {
	// At is the unix time in milliseconds when the timer should be executed.
	At int64 `json:"at"`

	// Call is the (one-way) call to make upon the timers execution.
	Call Call `json:"call"`

	// ChannelID is a channel ID that is used for expansion of the Call (optional).
	ChannelID string `json:"channel_id,omitempty"`
	// TeamID is a team ID that is used for expansion of the Call (optional).
	TeamID string `json:"team_id,omitempty"`
}

Ticket Link

https://mattermost.atlassian.net/browse/MM-48342

@hanzei hanzei added 2: Dev Review Requires review by a core committer 3: QA Review Requires review by a QA tester Docs/Needed Requires documentation labels Oct 28, 2022
@hanzei hanzei requested review from javaguirre and levb October 28, 2022 22:51
@hanzei hanzei added the Work In Progress Not yet ready for review label Oct 28, 2022
@hanzei
Copy link
Contributor Author

hanzei commented Oct 28, 2022

@levb @javaguirre The details are still WIP, but I would appreciate a high level review of the API.

@hanzei hanzei mentioned this pull request Nov 2, 2022
15 tasks
@levb
Copy link
Contributor

levb commented Nov 8, 2022

@hanzei would you please publish your mattermost-plugin-api branch? I did not find it.

server/httpin/call.go Outdated Show resolved Hide resolved
@@ -11,11 +11,29 @@ import (
"github.com/mattermost/mattermost-plugin-apps/utils/httputils"
)

// CallResponse contains everything the CallResponse struct contains, plus some additional
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

go.mod Outdated Show resolved Hide resolved
Copy link
Contributor

@javaguirre javaguirre left a comment

Choose a reason for hiding this comment

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

It would be nice if you add a code example in a comment of how we plan to use this to better understand everything. It's nice also for the future documentation. Thank you! 👍

@hanzei
Copy link
Contributor Author

hanzei commented Nov 9, 2022

@javaguirre The test app contains an example usage of timers.

@levb
Copy link
Contributor

levb commented Nov 9, 2022

@hanzei 0/5 and optional: would be nice to add a REST API test for this, at least for the happy path? If you need help figuring the tests out, I'd be happy to.

@hanzei hanzei removed the Work In Progress Not yet ready for review label Nov 10, 2022
@hanzei hanzei changed the title State full timers [MM-48342] Stateful timers Nov 10, 2022
Copy link
Contributor

@javaguirre javaguirre left a comment

Choose a reason for hiding this comment

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

I've made some sweeps on the code, but I need to spend more time on it, just wondering about your idea to change how setCaller works.

store: store,
// SetCaller must be called before calling any other methods of AppsServies.
// TODO: Remove this uggly hack.
func (a *AppServices) SetCaller(caller Caller) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can't we have a dummy function in the NewService set to something 'default' as a Caller, so we could set it up later when needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What would that default Caller look like?

Copy link
Contributor

@javaguirre javaguirre Nov 14, 2022

Choose a reason for hiding this comment

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

I think the problem is not creating a default for Caller, but AppServices doing too much, why not having different services providing their own business logic? I don't think a subscription app service would need any config to create a timer, right?

I need to think more about it, but if you want we can talk so I can be more helpful.

Copy link
Contributor

@javaguirre javaguirre Nov 17, 2022

Choose a reason for hiding this comment

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

Something like this is what I meant (I did it fast, so it's not accurate), let me know what you think and why/why not do this (I believe a decision was made before this PR, so I'm interested) :-)

// Copyright (c) 2020-present Mattermost, Inc. All Rights Reserved.
// See License for license information.

package appservices

import (
	"github.com/pkg/errors"

	"github.com/mattermost/mattermost-plugin-api/cluster"

	"github.com/mattermost/mattermost-plugin-apps/apps"
	"github.com/mattermost/mattermost-plugin-apps/server/config"
	"github.com/mattermost/mattermost-plugin-apps/server/incoming"
	"github.com/mattermost/mattermost-plugin-apps/server/store"
	"github.com/mattermost/mattermost-plugin-apps/utils"
)

type AppSubscriptionService interface {
	// Subscriptions

	Subscribe(*incoming.Request, apps.Subscription) error
	GetSubscriptions(*incoming.Request) ([]apps.Subscription, error)
	Unsubscribe(*incoming.Request, apps.Event) error
	UnsubscribeApp(*incoming.Request, apps.AppID) error
}

type AppSubscriptionService interface {
	// Timer

	CreateTimer(*incoming.Request, apps.Timer) error
}

type AppKVService interface {
    // KV

	KVSet(_ *incoming.Request, prefix, id string, data []byte) (bool, error)
	KVGet(_ *incoming.Request, prefix, id string) ([]byte, error)
	KVDelete(_ *incoming.Request, prefix, id string) error
	KVList(_ *incoming.Request, namespace string, processf func(key string) error) error
	KVDebugInfo(*incoming.Request) (*store.KVDebugInfo, error)
	KVDebugAppInfo(*incoming.Request, apps.AppID) (*store.KVDebugAppInfo, error)


}
	
type AppOAuthService interface {
    	// Remote (3rd party) OAuth2
	StoreOAuth2App(_ *incoming.Request, data []byte) error
	StoreOAuth2User(_ *incoming.Request, data []byte) error
	GetOAuth2User(_ *incoming.Request) ([]byte, error)
}

type Caller interface {
	InvokeCall(*incoming.Request, apps.CallRequest) (*apps.App, apps.CallResponse)
	NewIncomingRequest() *incoming.Request
}

type AppService struct {
	store     *store.Service


	conf config.Service
	log  utils.Logger
}

type AppTimerService struct {
    service *AppService
    
    scheduler *cluster.JobOnceScheduler
	caller    Caller
}

type AppSubscriptionService struct {
    service *AppService
}

@codecov-commenter
Copy link

codecov-commenter commented Nov 10, 2022

Codecov Report

Base: 20.78% // Head: 20.75% // Decreases project coverage by -0.04% ⚠️

Coverage data is based on head (496456e) compared to base (d5d265e).
Patch coverage: 0.00% of modified lines in pull request are covered.

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #375      +/-   ##
==========================================
- Coverage   20.78%   20.75%   -0.04%     
==========================================
  Files          79       80       +1     
  Lines        5445     5455      +10     
==========================================
  Hits         1132     1132              
- Misses       4181     4191      +10     
  Partials      132      132              
Impacted Files Coverage Δ
apps/timer.go 0.00% <0.00%> (ø)
server/proxy/service.go 6.97% <ø> (ø)

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

☔ View full report at Codecov.
📢 Do you have feedback about the report comment? Let us know in this issue.

type AppServices struct {
store *store.Service
store *store.Service
scheduler *cluster.JobOnceScheduler
Copy link
Contributor

Choose a reason for hiding this comment

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

Here's my biggest issue with this PR. JobOnceScheduler relies on KVList. plugin-apps stores all of the app keys in its keyspace (session and ouath2 tokens), so the list is going to be huge and KVList slow.

2/5 consider mattermost/mattermost#21653

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For reference: cluster.JobOnceScheduler calls KVList on Start() and every 5 minutes.

Copy link
Contributor Author

@hanzei hanzei Nov 14, 2022

Choose a reason for hiding this comment

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

Could you please elaborate on how a solution that uses mattermost/mattermost#21653 would work?

I would assume only the cluster leader executes timers, correct? Once a plugin instance receives the request to create a new timer, it needs to communicate that information to the leader. How would that work?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've talked with @levb offline. The solutions in #375 (comment) are the path forward, but require the cache store from #458.

@hanzei hanzei added this to the v1.3.0 milestone Dec 7, 2022
@hanzei
Copy link
Contributor Author

hanzei commented Jan 31, 2023

Notes from an offline discussion with @levb:

  • Notes receiving a job create event communicate to the leader the event
  • Leader writen to KV store
  • Also store a list of keys per interval (e.g. per hour)
  • Leader keeps a list of the jobs for the current interval in memory
  • The leader is the only one who executes

On leader change:

  • Read the current interval from KV store

@hanzei
Copy link
Contributor Author

hanzei commented Feb 21, 2023

Let's merge this and reiterate on store once #458 is merged.

@hanzei hanzei added 4: Reviews Complete All reviewers have approved the pull request QA Deferred Agreement with QA that these changes will be tested post-merge and removed 2: Dev Review Requires review by a core committer 3: QA Review Requires review by a QA tester labels Feb 21, 2023
@hanzei hanzei merged commit 72fbf7a into master Feb 21, 2023
@hanzei hanzei deleted the timer branch February 21, 2023 14:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
4: Reviews Complete All reviewers have approved the pull request Docs/Needed Requires documentation QA Deferred Agreement with QA that these changes will be tested post-merge
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants