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

feat: Add single mode in job #64

Closed
wants to merge 7 commits into from
Closed

Conversation

farwydi
Copy link

@farwydi farwydi commented Sep 23, 2020

What does this do?

This mode prevents double start.

@Streppel
Copy link
Member

Hey @farwydi, thanks for contributing. Let me see if I got it right: is this to prevent "overrunning" a job upon itself?

@farwydi
Copy link
Author

farwydi commented Sep 23, 2020

Yes sir, similar #63

@Streppel
Copy link
Member

Oh, cool! I believe this could make good use of singleflight, have you considered using it?

@dustin-decker
Copy link

It'd be nice if there were a good way to provide feedback when a job is scheduled to run but is still running. In resource-bound cases it could be a good indicator of resource starvation. I'm not sure of the best way to do this inside of a library though.

@Streppel
Copy link
Member

@farwydi did you take a look at a possible singleflight solution? I believe it'd be the way to go here

@farwydi farwydi force-pushed the master branch 2 times, most recently from e7189b1 to 3e17a67 Compare November 5, 2020 21:25
@farwydi
Copy link
Author

farwydi commented Nov 5, 2020

I didn't really understand the meaning TestSwap 🐰

This mode prevents double start.
@farwydi
Copy link
Author

farwydi commented Nov 5, 2020

@Streppel @dustin-decker I think this option will help us add more subtle settings with different behavior options in the future. How do you look at it?

dustin-decker
dustin-decker previously approved these changes Nov 5, 2020
Copy link

@dustin-decker dustin-decker left a comment

Choose a reason for hiding this comment

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

Please alter the fragile allocation, otherwise looks good to me.

var jobsBefore []Job
for _, p := range jb {
jobsBefore = append(jobsBefore, *p)
jobsBefore := make([]*Job, 2)

Choose a reason for hiding this comment

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

Setting this to 2 instead of len(jb) is fragile.

j.runCount++
switch j.runConfig.mode {
case SingletonMode:
_, j.err, _ = j.limiter.Do("main", func() (interface{}, error) {
Copy link
Member

@Streppel Streppel Nov 8, 2020

Choose a reason for hiding this comment

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

Just asking for the sake of it, but is this singleton mode related to the specific job in question? Because the way it is written here it will affect other singleton mode jobs as well, as they will all await for the "main" key. If that's not expected, we could change this key to something job-specific in order to identify each particular job.

Copy link
Member

Choose a reason for hiding this comment

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

Also, I'm sorry for taking so long to respond, I've been super busy nowadays :( will try to check in here more often

Copy link
Author

Choose a reason for hiding this comment

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

Each job has its own limiter singleflight.Group

func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool) {
	g.mu.Lock()
	if g.m == nil {
		g.m = make(map[string]*call)
	}
        // here
	if c, ok := g.m[key]; ok {

Streppel
Streppel previously approved these changes Nov 10, 2020
@Streppel
Copy link
Member

Cool @farwydi! I'd just say to update the README file as well

job.go Outdated Show resolved Hide resolved
example_test.go Outdated Show resolved Hide resolved
example_test.go Outdated Show resolved Hide resolved
job.go Outdated Show resolved Hide resolved
@@ -20,6 +22,23 @@ func TestTags(t *testing.T) {
assert.ElementsMatch(t, j.Tags(), []string{"tags", "tag", "some"})
}

func TestSingletonMode(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

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

this test itself is passing, and passes in a single test run, but when run in aggregate, the next test is failing. Seems the scheduler does in fact send out job that is then waiting on the singleflight to allow it to run, and that goroutine isn't properly stopped / canceled on s.Stop() - looking into this

Copy link
Contributor

Choose a reason for hiding this comment

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

indeed. Adding

	s.Stop()
+	time.Sleep(2 * time.Second)

sleep to the end of the test, confirms that a job is run after the scheduler is stopped.

Copy link
Contributor

Choose a reason for hiding this comment

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

so, it appears because the scheduler is scheduling another job, and it's sent to the singleflight.Do() which includes a wait group, even though the scheduler is stopped, the jobs aren't currently told about that stoppage.

I am interested in how best people view to solve this. I found a solution by adding a context with cancel into the job func itself:

func TestSingletonMode(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	s := NewScheduler(time.UTC)
	var trigger int32
	job, _ := s.Every(1).Second().Do(func() {
		select {
		case <-ctx.Done():
			return
		default:
			if atomic.LoadInt32(&trigger) == 1 {
				t.Fatal("Restart should not occur")
			}
			atomic.AddInt32(&trigger, 1)
			fmt.Println("I am a long task")
			time.Sleep(3 * time.Second)
		}
	})
	job.SingletonMode()
	s.StartAsync()
	time.Sleep(2 * time.Second)
	cancel()
	s.Stop()
	time.Sleep(2 * time.Second)
}

If we go this route - we're essentially saying, you the caller are responsible for making sure your job doesn't run again if you stop the scheduler.

I don't know that there is a way to build it into the library because we can't force jobs to take in a context that we can cancel, or even that the job has to stop when it's cancelled.

@JohnRoesler
Copy link
Contributor

This has been merged in #123

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.

None yet

4 participants