cron icon indicating copy to clipboard operation
cron copied to clipboard

once task

Open brucewang11 opened this issue 5 years ago • 15 comments

Could you run the task once?

brucewang11 avatar Apr 13 '20 11:04 brucewang11

No, but your task could remove itself from Cron when it starts running.

robfig avatar Apr 14 '20 16:04 robfig

This is my way of implementing a one-time task.

https://github.com/93Alliance/lodago/blob/master/crontab.go

you can use it in this way

c := lodago.NewCrontab()
go c.Start()
defer c.Stop()

t := lodago.CronTime{
	lodago.Once,
	"2020",
	"5",
	"8",
	"14",
	"19",
	"",
	"",
}
job1 := func() {
	fmt.Println("一次性任务")
}
c.AddJob(&t, job1)

for {
	time.Sleep(time.Duration(1 * time.Second))
	fmt.Println(c.GetEntries())
}

93Alliance avatar May 08 '20 06:05 93Alliance

I hava same problem. And I had implemented it.

Jinnrry avatar Jun 02 '20 09:06 Jinnrry

I hava same problem. And I had implemented it.

How did you do it?

93Alliance avatar Jun 02 '20 09:06 93Alliance

@93Alliance #317 Here

Jinnrry avatar Jun 03 '20 06:06 Jinnrry

How about using sync.Once?

For example you could do something like:

package main

import (
	"fmt"
	"github.com/robfig/cron/v3"
	"sync"
	"time"
)

func main() {
	kron := cron.New()
	once := sync.Once{}
	_, _ = kron.AddFunc("* * * * * ", func() {
		once.Do(func() {
			fmt.Println("Only prints once")
		})
	})
	kron.Start()
        defer kron.Stop()
	time.Sleep(time.Duration(5 * time.Minute))
	
}

Note that the instance of sync.Once{} needs to be created outside of the function passed to cron.AddFunc(). With this implementation the event/job is still firing but the code invoked only runs once.

buildscientist avatar Jun 04 '20 06:06 buildscientist

@buildscientist It is right. But the function will be called repeatedly. This is my code.

package main

import (
	"fmt"
	"github.com/robfig/cron/v3"
	"log"
	"os"
	"sync"
)

func main() {
	logger := log.New(os.Stdout, "DEBUG", log.Ldate|log.Ltime)
	wg := make(chan int64)

	kron := cron.New(cron.WithLogger(cron.VerbosePrintfLogger(logger)))
	once := sync.Once{}
	_, _ = kron.AddFunc("* * * * * ", func() {
		once.Do(func() {
			fmt.Println("Only prints once")
		})
	})
	kron.Start()
	defer kron.Stop()
	<-wg
}

And this is my logs:

DEBUG2020/06/04 15:25:07 start DEBUG2020/06/04 15:25:07 schedule, now=2020-06-04T15:25:07+08:00, entry=1, next=2020-06-04T15:26:00+08:00 DEBUG2020/06/04 15:26:00 wake, now=2020-06-04T15:26:00+08:00 Only prints once DEBUG2020/06/04 15:26:00 run, now=2020-06-04T15:26:00+08:00, entry=1, next=2020-06-04T15:27:00+08:00 DEBUG2020/06/04 15:27:00 wake, now=2020-06-04T15:27:00+08:00 DEBUG2020/06/04 15:27:00 run, now=2020-06-04T15:27:00+08:00, entry=1, next=2020-06-04T15:28:00+08:00 DEBUG2020/06/04 15:28:00 wake, now=2020-06-04T15:28:00+08:00 DEBUG2020/06/04 15:28:00 run, now=2020-06-04T15:28:00+08:00, entry=1, next=2020-06-04T15:29:00+08:00 DEBUG2020/06/04 15:29:00 wake, now=2020-06-04T15:29:00+08:00 DEBUG2020/06/04 15:29:00 run, now=2020-06-04T15:29:00+08:00, entry=1, next=2020-06-04T15:30:00+08:00 DEBUG2020/06/04 15:30:00 wake, now=2020-06-04T15:30:00+08:00 DEBUG2020/06/04 15:30:00 run, now=2020-06-04T15:30:00+08:00, entry=1, next=2020-06-04T15:31:00+08:00 DEBUG2020/06/04 15:31:00 wake, now=2020-06-04T15:31:00+08:00 DEBUG2020/06/04 15:31:00 run, now=2020-06-04T15:31:00+08:00, entry=1, next=2020-06-04T15:32:00+08:00 DEBUG2020/06/04 15:32:00 wake, now=2020-06-04T15:32:00+08:00 DEBUG2020/06/04 15:32:00 run, now=2020-06-04T15:32:00+08:00, entry=1, next=2020-06-04T15:33:00+08:00 DEBUG2020/06/04 15:33:00 wake, now=2020-06-04T15:33:00+08:00 DEBUG2020/06/04 15:33:00 run, now=2020-06-04T15:33:00+08:00, entry=1, next=2020-06-04T15:34:00+08:00 DEBUG2020/06/04 15:34:00 wake, now=2020-06-04T15:34:00+08:00 DEBUG2020/06/04 15:34:00 run, now=2020-06-04T15:34:00+08:00, entry=1, next=2020-06-04T15:35:00+08:00

Jinnrry avatar Jun 04 '20 07:06 Jinnrry

@Jinnrry

That is correct the function passed to the job does get called repeatedly but because it is wrapped in in sync.Once it'll only actually be called once.

As @robfig mentioned once the job has run you can remove it from the crontab.

buildscientist avatar Jun 04 '20 17:06 buildscientist

Why not? You can just implement Once schedule by yourself. Something like that should work:

package main 

import (
    "github.com/robfig/cron/v3"
)

type Task func()

func (t Task) Run() {
    t()
}

type OnceSchedule struct {
    called bool
}

func (o *OnceSchedule) Next(_ time.Time) time.Time {
    if o.called {
        return time.Time{}
    }

    o.called = true
    return time.Now()
}

func foo() {
    // do something...
}

func main() {
    kron := cron.New()
 
    kron.Schedule(new(OnceSchedule), Task(foo))

    kron.Start()
    defer kron.Stop()
}

sm4ll-3gg avatar Jul 28 '20 19:07 sm4ll-3gg

@small-egg Not a bad idea but your implementation isn't safe for concurrency. I would add a mutex to your OnceSchedule struct to insure you don't have 2 goroutines executing the job at the same time.

buildscientist avatar Jul 28 '20 19:07 buildscientist

@buildscientist thank you for your reply, but it's quite safe because setting of a bool variable is exactly one instruction of any processor, and there is the only one state switching to true.

sm4ll-3gg avatar Jul 28 '20 19:07 sm4ll-3gg

@small-egg In your example use case - yes,but what happens if I have multiple instances of kron doing different tasks followed by a task that should only be done once. This is a very common pattern where I have multiple jobs scheduled to do specified tasks with at least one job doing a certain task.

For example - I have 2 scheduled jobs each with their own instance returned by cron.New(). Both jobs do different tasks but only one should do Task(foo).

In this scenario you could have both jobs that complete at the same time (on a multi-core box) and attempt to run Task(foo) at the same time leading to a potential race condition.

I realize my example might seem "convoluted" but this is a library which could potentially be used in the manner I just described.

Also note that the documentation clearly states:

Since the Cron service runs concurrently with the calling code, some amount of care must be taken to ensure proper synchronization.

All cron methods are designed to be correctly synchronized as long as the caller ensures that invocations have a clear happens-before ordering between them.

@robfig any thoughts on this :)

buildscientist avatar Jul 29 '20 23:07 buildscientist

@small-egg In your example use case - yes,but what happens if I have multiple instances of kron doing different tasks followed by a task that should only be done once. This is a very common pattern where I have multiple jobs scheduled to do specified tasks with at least one job doing a certain task.

For example - I have 2 scheduled jobs each with their own instance returned by cron.New(). Both jobs do different tasks but only one should do Task(foo).

In this scenario you could have both jobs that complete at the same time (on a multi-core box) and attempt to run Task(foo) at the same time leading to a potential race condition.

I realize my example might seem "convoluted" but this is a library which could potentially be used in the manner I just described.

Also note that the documentation clearly states:

Since the Cron service runs concurrently with the calling code, some amount of care must be taken to ensure proper synchronization. All cron methods are designed to be correctly synchronized as long as the caller ensures that invocations have a clear happens-before ordering between them.

@robfig any thoughts on this :)

The library provides several common kinds of schedule implementation. If you want any other implementation, you can do it yourself due to Schedule interface implementation of that you should pass to the cron.(*Cron).Schedule method. Your example is really unusual, so it's not surprising, that the library doesn't have the support of it out of the box.

In your case, you can implement a schedule with an exclusive lock, and pass it to both krons to prevent multiple jobs from launching at the same time.

sm4ll-3gg avatar Aug 10 '20 14:08 sm4ll-3gg

No, but your task could remove itself from Cron when it starts running.

To remove itself, how to get itself entryId in the running job?

chowyi avatar Jun 09 '22 08:06 chowyi