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

remove Start() method #11

Closed
janrnc opened this issue Sep 1, 2023 · 6 comments
Closed

remove Start() method #11

janrnc opened this issue Sep 1, 2023 · 6 comments
Labels
good first issue Good for newcomers

Comments

@janrnc
Copy link
Member

janrnc commented Sep 1, 2023

Cron provides two similar methods: Run() and Start().

cron/cron.go

Lines 214 to 235 in 5eb0ef7

// Start the cron scheduler in its own goroutine, or no-op if already started.
func (c *Cron) Start() {
c.runningMu.Lock()
defer c.runningMu.Unlock()
if c.running {
return
}
c.running = true
go c.run()
}
// Run the cron scheduler, or no-op if already running.
func (c *Cron) Run() {
c.runningMu.Lock()
if c.running {
c.runningMu.Unlock()
return
}
c.running = true
c.runningMu.Unlock()
c.run()
}

I think that calling go cron.Run() the behavior would be the same as cron.Start(). IMO we could remove Start() and keep just the Run() method, letting the user decide the execution goroutine on its own.

@janrnc janrnc added the good first issue Good for newcomers label Sep 1, 2023
@rameshgkwd05
Copy link

I think they look similar, but they are not same as Start facilitates running cron scheduler in its own goroutine while Run()-> runs the cron scheduler in current goroutine.

@janrnc
Copy link
Member Author

janrnc commented Sep 2, 2023

Hi @rameshgkwd05,
I agree. What I'm proposing is a matter of API design. Assuming that calling go cron.Run() the behavior would be the same as using cron.Start(), IMHO exposing a single blocking method reduces the API surface and makes the usage more clear from a Go perspective. In order to run the cron scheduler in its own goroutine you would have to add the go keyword in front, as you would do for any other function. What do you think?

@rameshgkwd05
Copy link

Hi @janfranchini
By looking at history at original cron repo, I saw that Run() was added later thats why it resonates partially with your point of confusion in API design. Originally, Start, Stop, Addfunc,.. interfaces were created and they go well with the cron scheduler running in go routine.

About keeping Start or Run, I would go with Start as we usually run cron job in background, so does the Start method.
Also it frees API consumers of worries of explicitly calling go Start() to start a cron job. It also help to write an easy go tests for the caller functions.

@janrnc
Copy link
Member Author

janrnc commented Sep 3, 2023

There is for sure a reasonable motivation behind the current API. We are starting from scratch in terms of stable releases and the intent, for now, is to review the current state of the code.

About keeping Start or Run, I would go with Start as we usually run cron job in background

This is a good point. I think too that the background usage would cover most (almost every IMO) of the cases. But as you said, cron.Run() was added later and this makes me think that this blocking feature was somehow wanted and appreciated. Unfortunately I didn't find any discussion motivating this change.

Now, the point is that of finding a trade-off between having a single exposed method and both use cases being simple to deal with.

  • Keeping only cron.Start(): in order to behave as blocking you would need to stop the execution after the scheduler started. See how to start? robfig/cron#143
  • Keeping only cron.Run(): in order to behave as non-blocking you would have to use go cron.Run().

This all said, both ways sound fine to me, I definitely appreciate the idea of providing what's going to be used most of the time. It's more a style decision. @Zavy86, @inalto, what are your preferences?

Once decided, @rameshgkwd05 would you like to open a PR for this?

@janrnc
Copy link
Member Author

janrnc commented Sep 3, 2023

A use case I didn't consider is that of stopping the scheduler when using the first option I listed with blocking behavior. select {} blocks forever, instead I would expect the execution to resume in case the scheduler is stopped. For making this work with the first option we would need some communication mechanism so that externally it's possible to wait this event instead of waiting indefinitely.

Using the second solution this behavior would be for free. This point makes me lean towards the second option.

@janrnc
Copy link
Member Author

janrnc commented Dec 16, 2023

While trying to remove the Start() method I realized a side effect of using go cron.Run().

cron/cron_test.go

Lines 93 to 112 in abf1acd

// Start, stop, then add an entry. Verify entry doesn't run.
func TestStopCausesJobsToNotRun(t *testing.T) {
wg := &sync.WaitGroup{}
wg.Add(1)
cron := newWithSeconds()
cron.Start()
cron.Stop()
_, err := cron.Add("* * * * * ?", func() { wg.Done() })
if err != nil {
t.Error("non-nil error")
}
select {
case <-time.After(OneSecond):
// No job ran!
case <-wait(wg):
t.Fatal("expected stopped cron does not run any job")
}
}

Consider the change when starting the scheduler in the above test: without goroutine synchronization, the scheduler could start after the Stop() and/or Add() calls, resulting in unpredictable behavior. I don't think adding extra complexity to this makes sense, so I'm closing for now.

@janrnc janrnc closed this as completed Dec 16, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
good first issue Good for newcomers
Projects
None yet
Development

No branches or pull requests

2 participants