Giter Site home page Giter Site logo

go-co-op / gocron Goto Github PK

View Code? Open in Web Editor NEW
5.2K 34.0 296.0 833 KB

Easy and fluent Go cron scheduling. This is a fork from https://github.com/jasonlvhit/gocron

License: MIT License

Go 99.70% Makefile 0.30%
cron scheduler gocron golang-job-scheduling golang clockwork schedule hacktoberfest

gocron's Introduction

gocron: A Golang Job Scheduling Package

CI State Go Report Card Go Doc

gocron is a job scheduling package which lets you run Go functions at pre-determined intervals.

If you want to chat, you can find us on Slack at

Quick Start

go get github.com/go-co-op/gocron/v2
package main

import (
	"fmt"
	"time"

	"github.com/go-co-op/gocron/v2"
)

func main() {
	// create a scheduler
	s, err := gocron.NewScheduler()
	if err != nil {
		// handle error
	}

	// add a job to the scheduler
	j, err := s.NewJob(
		gocron.DurationJob(
			10*time.Second,
		),
		gocron.NewTask(
			func(a string, b int) {
				// do things
			},
			"hello",
			1,
		),
	)
	if err != nil {
		// handle error
	}
	// each job has a unique id
	fmt.Println(j.ID())

	// start the scheduler
	s.Start()

	// block until you are ready to shut down
	select {
	case <-time.After(time.Minute):
	}

	// when you're done, shut it down
	err = s.Shutdown()
	if err != nil {
		// handle error
	}
}

Examples

Concepts

  • Job: The job encapsulates a "task", which is made up of a go function and any function parameters. The Job then provides the scheduler with the time the job should next be scheduled to run.
  • Scheduler: The scheduler keeps track of all the jobs and sends each job to the executor when it is ready to be run.
  • Executor: The executor calls the job's task and manages the complexities of different job execution timing requirements (e.g. singletons that shouldn't overrun each other, limiting the max number of jobs running)

Features

Job types

Jobs can be run at various intervals.

  • Duration: Jobs can be run at a fixed time.Duration.
  • Random duration: Jobs can be run at a random time.Duration between a min and max.
  • Cron: Jobs can be run using a crontab.
  • Daily: Jobs can be run every x days at specific times.
  • Weekly: Jobs can be run every x weeks on specific days of the week and at specific times.
  • Monthly: Jobs can be run every x months on specific days of the month and at specific times.
  • One time: Jobs can be run at specific time(s) (either once or many times).

Concurrency Limits

Jobs can be limited individually or across the entire scheduler.

  • Per job limiting with singleton mode: Jobs can be limited to a single concurrent execution that either reschedules (skips overlapping executions) or queues (waits for the previous execution to finish).
  • Per scheduler limiting with limit mode: Jobs can be limited to a certain number of concurrent executions across the entire scheduler using either reschedule (skip when the limit is met) or queue (jobs are added to a queue to wait for the limit to be available).
  • Note: A scheduler limit and a job limit can both be enabled.

Distributed instances of gocron

Multiple instances of gocron can be run.

  • Elector: An elector can be used to elect a single instance of gocron to run as the primary with the other instances checking to see if a new leader needs to be elected.
    • Implementations: go-co-op electors (don't see what you need? request on slack to get a repo created to contribute it!)
  • Locker: A locker can be used to lock each run of a job to a single instance of gocron. Locker can be at job or scheduler, if it is defined both at job and scheduler then locker of job will take precedence.
    • Implementations: go-co-op lockers (don't see what you need? request on slack to get a repo created to contribute it!)

Events

Job events can trigger actions.

Options

Many job and scheduler options are available.

  • Job options: Job options can be set when creating a job using NewJob.
  • Global job options: Global job options can be set when creating a scheduler using NewScheduler and the WithGlobalJobOptions option.
  • Scheduler options: Scheduler options can be set when creating a scheduler using NewScheduler.

Logging

Logs can be enabled.

  • Logger: The Logger interface can be implemented with your desired logging library. The provided NewLogger uses the standard library's log package.

Metrics

Metrics may be collected from the execution of each job.

  • Monitor: A monitor can be used to collect metrics for each job from a scheduler.
    • Implementations: go-co-op monitors (don't see what you need? request on slack to get a repo created to contribute it!)

Testing

The gocron library is set up to enable testing.

Supporters

We appreciate the support for free and open source software!

This project is supported by:

Star History

Star History Chart

gocron's People

Contributors

alphanecron avatar cloudkucooland avatar dependabot[bot] avatar drewgonzales360 avatar evgenymarkov avatar giri-vsr avatar higan avatar johnroesler avatar leedrum avatar manuelarte avatar moyu-x avatar pcfreak30 avatar rbroggi avatar samuelattwood avatar trungdlp-wolffun avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

gocron's Issues

Investigate other possible race conditions

Describe the bug

Running go test -race shows some race conditions. Need to determine if those are in the tests only, or if they're valid race conditions in the code.

To Reproduce

go test -race github.com/go-co-op/gocron

Version

daf5304

Expected behavior

Additional context

[BUG] - All Jobs Start Immediately

Describe the bug

I have three jobs which I want to run at a specific time of day and a specific day of the month. None of them have StartImmediately() so I would not expect them to run whenever they get scheduled.

To Reproduce

Steps to reproduce the behavior:

  1. scheduler.Every(1).Day().At("00:30").Do()
  2. Expect the job to not run
  3. See the logs of the job running

Version

v0.3.1 from go.mod file

Expected behavior

The expected behavior is that because I do not have StartImmediately() attached to it they would not run immediately at the start up and instead would only run at the times scheduled.

Additional context

Screen Shot 2020-09-15 at 8 17 09 AM

Tests are deterministic

copy/paste from here

Current implementation depends too much on using time manipulation and because of that some tests take a long time to run (testTaskAt is one example, as it depends on the system changing minute, even though I believe we could fix this by testing it differently. Another example is the recent locking implementation).

This also causes some weird bugs that happen only at certain times (I've seen cases where time.Now().Minute() + 1 was used to compare times, causing failures if you ran the test suite anytime at HH:59

We should find a way to end this by wrapping the system clock and thus being able to mock and control it.

Further reading

[BUG] - s1.StartAsync() from example doesn't work in latest release

Describe the bug

Tried using the example in the readme on a project with go modules. It automatically assigns the latest release tag of v0.1.1 in v0.1.1 the method StartAsync does not exist.

To Reproduce

Steps to reproduce the behavior:

  1. go to a project that uses go modules
  2. add the example from the readme
  3. Try to compile project. You will get the error s1.StartAsync undefined (type *gocron.Scheduler has no field or method StartAsync)

Version

v0.1.1

Expected behavior

The example on the readme should compile with go modules.

Additional context

N/A

Data race

There is a data race problem with this lib

image

[BUG] - Runs task twice

Hi everyone, I am trying to integrate gocron in my service. But when I tried to run task it is running it twice.

package services

import (
	"fmt"
	"time"

	"github.com/go-co-op/gocron"
)

func task() {
	fmt.Println("I am running task.")
}
func main() {
	s1 := gocron.NewScheduler(time.UTC)
	s1.Every(1).Monday().Do(task)
	s1.StartAsync()
        select{}
}
Output:
I am running task.
I am running task.

Is there something that I am missing or is it a bug?

Thanks you very much.

[BUG] - s.startAt(..) doesn't work, since s.Do() rewrites job.nextRun field

Describe the bug

s.startAt(..) doesn't work, since s.Do() rewrites job.nextRun field

To Reproduce

scheduler.Every(3).Second().StartAt(time.Now().Add(11 * time.Second)).Do(s.bookkeeping)

scheduler.StartAsync()

Starts job in 3 seconds after start.

Version

github.com/go-co-op/gocron v0.1.2-0.20200429025551-8c7e3da6cc03

Expected behavior

Start in 11 seconds, then run every 3.

[FEATURE] - Remove Job from Scheduler Jobs when the Job is supposed to be launch only once

Is your feature request related to a problem? Please describe

I've have a Job that i would like to run only once at a specific date

job, err = scheduler.Every(1).StartAt(date).SetTag(tag).Do(task)
...
job.LimitRunsTo(1)

However when the Job has run i still have a reference to it in the scheduler scheduler.Jobs()

I have to manually remove this specific Job in my task when the Job is done.

scheduler.RemoveJobByTag(tag)

Because if i don't, and if i run a second Job after this, then the second job will never be launched.

Describe the solution you'd like

When we choose to limit the job to be runned only once ( job.LimitRunsTo(1) ) and its execution has been done then remove this Job from the scheduler Jobs.

Are you later adding jobs to the running scheduler? Yes

[1] My program runs the scheduler

scheduler.StartAsync()

job, err = scheduler.Every(1).StartAt(date).SetTag(tag).Do(task)
...
job.LimitRunsTo(1)
[2] today at 17h40 I send a request in order to run a Job1 at the specific date ( today 17:45 ) :

Sending request with date ( today 17:45 ) ...

[3] today at 17h41 I send a another request in order to run a second Job2 at the specific date ( today 17:50 ) :

Sending request with date ( today 17:50 ) ...

[4] results
today 17:45 Job1 , correctly executed task
today 17:50 Job2 , never executed

Implement an interface for time

type Time interface {
    Now() time.Time
    After(d time.Duration) <-chan time.Time
    ...
}

type realTime struct{}
func (realTime) Now() time.Time { return time.Now() }
func (realTime) After(d time.Duration) <-chan time.Time { return time.After(d) }

type fakeTime struct{}
func (fakeTime) Now() time.Time { return ... }
func (fakeTime) After(d time.Duration) <-chan time.Time { return ... }

[FEATURE] - Delayed start

now that the default to startsimmediately is true & working

We can say what interval a job should run at, but not that it should start after that first interval, TTBOMK

would like to have a trivial way to set that to false, so it starts at the next "every" cycle

scheduler.AfterEvery(x).Seconds().Do(...)

Start a PR

Happy to create a trivial PR for this - just wanted to see if there was some reason that flipping the default to startsImmediate:true w/o offering a way to opt-out of that was... needed for some reason or would automatically be rejected, or there is a better way within the existing system that I'm not aware of?

We're using the scheduler for background tasks

...and I would prefer that the server be 100% available to live clients initially - and then have the background tasks kick in on their scheduled intervals

[BUG] weekdays only work for weekly basis schedules

Describe the bug

gocron.Every(2).Thursday().At("08:00").Do(taskHandler) does not work because we do not consider the interval on our weekday API.

Expected behavior

gocron.Every(2).Thursday().At("08:00").Do(taskHandler) should execute every 2 thursdays as soon as the scheduler begins to run

Additional context

Do we run at the first given thursday, then skip two? Or begin skipping two? Ideas of how to approach this are welcome

originally posted here

[BUG] - Lock service is not working

Hi every one, i was checking that the feature for preventing run a task twice using lock service is not working at all. In the function func (s *Scheduler) run(job *Job) error at line 180, when call to locker.Lock(key) the response is ignored, so if lock() return false the job can be executed.

func (s *Scheduler) run(job *Job) error {
	if job.lock {
		if locker == nil {
			return fmt.Errorf("trying to lock %s with nil locker", job.jobFunc)
		}
		key := getFunctionKey(job.jobFunc)

		locker.Lock(key)
		defer locker.Unlock(key)
	}

	job.lastRun = s.time.Now(s.loc)
	go job.run()

	return nil
}

Is there a scenario I am not seeing or is it a bug?

Thanks you very much.

[BUG] - schedule job at specific time

Describe the bug

I start a scheduler in my local timezone. Then I read the scheduled jobs from a struct in settings.
I receive the start time as a json timestamp, which is converted to a Golang Time. In the printed output, it seems like this conversion works the way it should.

Also when I ask when the next scheduled job is going to run, I see the correct time.

Alas, when the time comes to run the job, nothing happens.

If I add a recurring job to the queue, say every 2 seconds, I do see the function getting executed with the parameters that were passed.

I also see the tag, I added to the job.

Any idea what I might be doing wrong?

Jo

To Reproduce

Steps to reproduce the behavior:
`

func main() {
location, _ := time.LoadLocation("Europe/Brussels")
scheduler := gocron.NewScheduler(location)

for _, task := range settings.Schedule {
	PrettyPrint(task)
	now := nowFormatted()
	if task.StartDate > now {
		var DateRE = regexp.MustCompile(`/Date\((?P<UnixTimestamp>\d+)(?P<TZOffset>[-\+]\d+)\)`)
		tsString := DateRE.FindStringSubmatch(task.StartDate)[1]
		timestamp, _ := strconv.ParseInt(tsString, 10, 64)
		startTime := time.Unix(timestamp/1000, 0)
		job, err := scheduler.Every(1).Day().StartAt(startTime).SetTag([]string{task.ID}).Do(taskWithParams, task)
		// scheduler.Every(2).Seconds().Do(taskWithParams, task)
		PrettyPrint(tsString)
		PrettyPrint(timestamp)
		PrettyPrint(startTime.Format("Mon Jan 2 15:04:05 -0700 MST 2006"))
		_, tt := scheduler.NextRun()
		log.Warn("Next run")
		PrettyPrint(tt.Format("Mon Jan 2 15:04:05 -0700 MST 2006"))
		if err != nil {
			log.Error(err)
		}
		if task.RecurrencePattern == "" {
			job.LimitRunsTo(1)
			job.RemoveAfterLastRun()
		}
scheduler.StartAsync()

}

`
Output:

{ "Action": 0, "EndDate": "/Date(1608919200000+0100)/", "Filename": "/home/pi/doSomethingImportant.sh", "Id": "8a102123-a91d-474b-8f83-547c9cdcf3ad", "Parameters": "", "ProcedureName": "main", "RecurrencePattern": "", "ScheduleType": 0, "StartDate": "/Date(1608918720000+0100)/" } "1608918720000" 1608918720000 "Fri Dec 25 18:52:00 +0100 CET 2020" WARN[25/12/2020 18:50:35] Next run "Fri Dec 25 18:52:00 +0100 CET 2020" [ "8a102123-a91d-474b-8f83-547c9cdcf3ad" ]

[QUESTION] - Best way to update events jobs

Hello,

Each morning I need to adjust the time for my jobs.

What is the best way to update jobs:

  1. Simply clear and re-add the job, even if the scheduler is already started?
 s1 := gocron.NewScheduler(time.UTC)
 s1.Every(1).Day().At("10:30").Do(task)
 s1.StartAsync()
...
s1.Clear()
 s1.Every(1).Day().At("11:00").Do(task)
  1. or recreate the scheduler (is there a clean way to stop and remove scheduler?)
 s1 := gocron.NewScheduler(time.UTC)
 s1.Every(1).Day().At("10:30").Do(task)
 s1.StartAsync()
...
s1.Clear()
 s1 = gocron.NewScheduler(time.UTC)
 s1.Every(1).Day().At("11:00").Do(task)
 s1.StartAsync()
  1. Another way?

Thanks

[BUG] - Weird behavior with scheduler

Describe the bug

I set the scheduler to run a task every 5 minutes and output the time. It worked fine for a 3 or 4 hours and then appeared to skip intervals. Below is the output of time for each interval (not all times are shown below only a snippet). You will see that after 18:43:05 it skipped intervals and ran again at 19:05:55 and then skipped more 5 minute intervals until 19:42:38 and then started working correctly again.

2020-04-13 16:48:05.838436 -0400 EDT m=+13500.009954783
2020-04-13 16:53:05.838527 -0400 EDT m=+13800.009911408
2020-04-13 16:58:05.839164 -0400 EDT m=+14100.010415294
2020-04-13 17:03:05.838805 -0400 EDT m=+14400.009986272
2020-04-13 17:08:05.839337 -0400 EDT m=+14700.010392392
2020-04-13 17:13:05.839008 -0400 EDT m=+15000.009937232
2020-04-13 17:18:05.839073 -0400 EDT m=+15300.009875817
2020-04-13 17:23:05.839285 -0400 EDT m=+15600.009961418
2020-04-13 17:28:05.830553 -0400 EDT m=+15900.001102576
2020-04-13 17:33:05.830578 -0400 EDT m=+16200.001001475
2020-04-13 17:38:05.838066 -0400 EDT m=+16500.008315243
2020-04-13 17:43:05.834449 -0400 EDT m=+16800.004566250
2020-04-13 17:48:05.83347 -0400 EDT m=+17100.003456543
2020-04-13 17:53:05.833975 -0400 EDT m=+17400.003829427
2020-04-13 17:58:05.838544 -0400 EDT m=+17700.008267980
2020-04-13 18:03:05.83932 -0400 EDT m=+18000.008912112
2020-04-13 18:08:05.838824 -0400 EDT m=+18300.008285020
2020-04-13 18:13:05.838847 -0400 EDT m=+18600.007748931
2020-04-13 18:18:05.840015 -0400 EDT m=+18900.008736021
2020-04-13 18:23:05.839333 -0400 EDT m=+19200.007873869
2020-04-13 18:28:05.839628 -0400 EDT m=+19500.007987396
2020-04-13 18:33:05.839701 -0400 EDT m=+19800.007879974
2020-04-13 18:38:05.841204 -0400 EDT m=+20100.009207320
2020-04-13 18:43:05.839963 -0400 EDT m=+20400.007780207
2020-04-13 19:05:55.227962 -0400 EDT m=+20598.007768713
2020-04-13 19:42:38.009791 -0400 EDT m=+20652.365654121
2020-04-13 19:47:38.433487 -0400 EDT m=+20952.006136178
2020-04-13 19:52:38.433824 -0400 EDT m=+21252.006190879
2020-04-13 19:57:38.433235 -0400 EDT m=+21552.005318678
2020-04-13 20:02:38.426426 -0400 EDT m=+21852.004416490
2020-04-13 20:07:38.423611 -0400 EDT m=+22152.001319491

To Reproduce

Here is the code

package main

import (
"fmt"
"time"

"github.com/go-co-op/gocron"

)

func task() {
start := time.Now()
fmt.Println(start)
}

func taskWithParams(a int, b string) {
fmt.Println(a, b)
}

func main() {
// defines a new scheduler that schedules and runs jobs
s1 := gocron.NewScheduler(time.UTC)

s1.Every(5).Minutes().Do(task)
s1.StartBlocking()

}

Expected behavior

Should be running every 5 minutes and not skipping.

Additional context

Feature request: Store job/schedule data

Thanks for awesome library that really helps :)

Maybe it will be good idea.
In some cases people neen to run task only once.
Yes i can run task and then remove.

And also. What will happend if there are 30 tasks in scheduler and app crashed. After restart we will lose all tasks.

If it will be good idea, can try to do this

[FEATURE] - Maximum concurrent jobs

Is your feature request related to a problem? Please describe

When running resource intensive jobs, it may be desirable to limit how many are executed at once.

Describe the solution you'd like

I would like to be able to configure maximum running jobs.

Describe alternatives you've considered

I can probably do a hacky implementation of this with the Locker feature.

[BUG] - removeByCondition get "slice bounds out of range"

Describe the bug

Loop in removeByCondition (scheduler.go) will keep iterating by the original value because the range loop copies the values from the slice to a local variable n; updating n will not affect the slice. It will result in panic because slice bounds out of range when the index of iteration goes beyond the current length of s.jobs

To Reproduce

Steps to reproduce the behavior:

  1. Run example code
  2. Will result in this error:
2020-07-11 08:00:11 +0000 UTC
panic: runtime error: slice bounds out of range [11:10]

goroutine 1 [running]:
github.com/go-co-op/gocron.removeAtIndex(...)
	/home/ridwanakf/go/pkg/mod/github.com/go-co-op/[email protected]/scheduler.go:248
github.com/go-co-op/gocron.(*Scheduler).removeByCondition(0xc000032100, 0xc000098da8)
	/home/ridwanakf/go/pkg/mod/github.com/go-co-op/[email protected]/scheduler.go:218 +0x23b
github.com/go-co-op/gocron.(*Scheduler).Remove(0xc000032100, 0x4dfda0, 0x515080)
	/home/ridwanakf/go/pkg/mod/github.com/go-co-op/[email protected]/scheduler.go:203 +0x66
main.main()
	/home/ridwanakf/go/src/github.com/ridwanakf/personal-go-projects/cron/main.go:78 +0x1340
exit status 2

Version

commit SHA: 4cc1bfb

Expected behavior

Should delete all expected jobs without resulting in panic

Additional context

[FEATURE] - Add golangci-lint GitHub Action

Is your feature request related to a problem? Please describe

Using popular lint tool golangci-lint would help to keep the code cleaner and fix the already existing issues.

Describe the solution you'd like

  • Add .github/workflows/golangci-lint.yml
  • Remove lint, fmt, check-fmt and vet job from .github/workflows/go_test.yml
  • Remove fmt, check-fmt and vet from Makefile
    • Optionally keep lint but to call golangci-lint
  • Create a minimal ~ permissive .golangci.yml as starting point
  • Fix errors reported by the default configuration
  • Enable more linters and fix issues reported by the newly activated linters

Example YAML based on the official repo and on your current workflow https://github.com/golangci/golangci-lint-action#how-to-use

name: golangci-lint
on:
  push:
    branches:
      - master
  pull_request:
    branches:
      - master
jobs:
  golangci:
    strategy:
      matrix:
        go-version:
          - 1.14
          - 1.15
    name: lint
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: golangci-lint
        uses: golangci/golangci-lint-action@v2
        with:
          version: v1.33

Describe alternatives you've considered

The code already provides an alternative: golang.org/x/lint/golint

Additional context

N/A

v1 release goals

  • Enhance all time related tests
  • Fix bug where fast running jobs are executed twice, or jobs are executed at the wrong time
  • Examples and documentation cleaned up
  • ...

[FEATURE] - Ability to reschedule a task

Is your feature request related to a problem? Please describe

I want to be able to change the scheduling time based on the settings received while executing a task

Describe the solution you'd like

I tried adding a separate channel to capture a message on task frequency change and clearing the existing task and the adding a new one with the new frequency. However this causes race conditions.

[BUG] - Unable to get v0.2 version using go mod

Describe the bug

Unable to get v0.2 version using go mod. The latest version I was able to get is (v0.1.1). Was it due to naming convention of the version?

  1. Terminal show an error when using v0.2 -> no matching versions for query "v0.2"

  2. Terminal show an error when using v0.2.0 -> unknown revision v0.2.0

Run a job at a specific time with milliseconds

Currently we can just run a job at a specific time in HH:MM:SS format, I wanted to know is it possible to run a job at specific time in HH:MM:SS.MS format?
e.g : scheculer.Every(1).Day().At("2:49:51.01").Do(task)

[FEATURE] - Create a roadmap

Is your feature request related to a problem? Please describe

Describe the solution you'd like

Describe alternatives you've considered

Additional context

scheduleNextRun when the job create after schedule started

job run immediately when I remove the job and create job again after schedule started。

expect:
scheduleNextRun when the job create after schedule started
or
add new funcion 'UpdateInterval'

testing:

package main

import (
	"fmt"
	"time"

	"github.com/go-co-op/gocron"
)

var (
	lastTime time.Time
	s        *gocron.Scheduler
)

func task() {
	now := time.Now()
	fmt.Println(now.Sub(lastTime).Seconds())
	lastTime = now
	s.Remove(task)
	s.Every(3).Seconds().Do(task)
}

func main() {
	s = gocron.NewScheduler(time.UTC)
	_close := s.StartAsync()

	lastTime = time.Now()
	s.Every(3).Seconds().Do(task)

	time.AfterFunc(time.Duration(time.Second*10), func() { s.Stop() })

	<-_close
}

[BUG] - job executes every second when scheduled for more seconds

Describe the bug

Reported by Hari in slack here (thank you!)

A job is scheduled every 8 seconds, but runs every second.

To Reproduce

package main
import (
	"fmt"
	"time"
	"strconv"
	"github.com/go-co-op/gocron"
)
func task() {
    fmt.Println(time.Now().Format("01-02-2006 15:04:05") + " ----- " + "I am running task.")
}
func taskWithParams(id string) {
    fmt.Println(time.Now().Format("01-02-2006 15:04:05") + " ----- " + "task id : " + id)
}
func main() {
    s := gocron.NewScheduler(time.UTC)
    fmt.Println(time.Now().Format("01-02-2006 15:04:05"))
    s.StartAsync()
    _, _ = s.Every(1).Second().StartImmediately().Do(taskWithParams, strconv.Itoa(1))
    _, _ = s.Every(8).Second().StartImmediately().Do(taskWithParams, strconv.Itoa(2))
    time.Sleep(10 * time.Second)
}

output

go run a.go
11-24-2020 06:04:19
11-24-2020 06:04:20 ----- task id : 1
11-24-2020 06:04:20 ----- task id : 2
11-24-2020 06:04:21 ----- task id : 2
11-24-2020 06:04:21 ----- task id : 1
11-24-2020 06:04:22 ----- task id : 1
11-24-2020 06:04:22 ----- task id : 2
11-24-2020 06:04:23 ----- task id : 2
11-24-2020 06:04:23 ----- task id : 1
11-24-2020 06:04:24 ----- task id : 1
11-24-2020 06:04:24 ----- task id : 2
11-24-2020 06:04:25 ----- task id : 2
11-24-2020 06:04:25 ----- task id : 1
11-24-2020 06:04:26 ----- task id : 1
11-24-2020 06:04:26 ----- task id : 2
11-24-2020 06:04:27 ----- task id : 1
11-24-2020 06:04:27 ----- task id : 2
11-24-2020 06:04:28 ----- task id : 1
11-24-2020 06:04:28 ----- task id : 2

Version

140559b

Expected behavior

Generated the expected output on 0969260

11-24-2020 09:07:45
11-24-2020 09:07:46 ----- task id : 2
11-24-2020 09:07:46 ----- task id : 1
11-24-2020 09:07:47 ----- task id : 1
11-24-2020 09:07:48 ----- task id : 1
11-24-2020 09:07:49 ----- task id : 1
11-24-2020 09:07:50 ----- task id : 1
11-24-2020 09:07:51 ----- task id : 1
11-24-2020 09:07:52 ----- task id : 1
11-24-2020 09:07:53 ----- task id : 1
11-24-2020 09:07:54 ----- task id : 2
11-24-2020 09:07:54 ----- task id : 1

Additional context

Is Every(1).Day().At working properly?

Wrote a simple code

package main

import (
	"fmt"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/go-co-op/gocron"
)

func daily() {
	fmt.Println("Daily ", time.Now())
}

func main() {	
	s2 := gocron.NewScheduler(time.UTC)	
	s2.Every(1).Day().At("17:40").Do(daily)	
	s2.Every(1).Day().At("17:41").Do(daily)
	
	s2.StartAsync()

	c := make(chan os.Signal, 1)
	signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
	for sig := range c {		
		fmt.Printf("Signal stop >> %s", sig.String())		
		os.Exit(1)
	}	
}

Expecting that if porgram started at 17:38, at 17:40 and 17;41 the message will appear. But nothing happens. What I am doing wrong?

[FEATURE] - Time format allow wildcards

First of all - awesome project, just what I needed in my life πŸ˜ƒ

It seems like there's error on time format for when defining on which minute cronjob should start at
eg:
scheduler.Every(1).Hour().At(":00").Lock().Do(MySuperCoolMethodToDeleteOldStuff)
or
scheduler.Every(1).Hour().At("**:30").Lock().Do(MySuperCoolMethodToDeleteOldStuff)

When checking https://github.com/dbader/schedule then I can see that the time format is ":00", but on ruby clockwork https://github.com/Rykian/clockwork#event-parameters then the format is "**:30".
I was hoping that similar things would work on this as well, but it seems now to me moreof like a feature request.

What I intend to do is to sync the deployments to start at same time, so only one instance of the service will execute the method.

So, that job MySuperCoolMethodToDeleteOldStuff would run at
12:00
13:00
14:00
etc..

[BUG] - Panic on malformed time is inscrutable.

Describe the bug

The library panics if a time is malformed. Specifically in this case if the user forgets a preceeding zero, which I feel is probably a relatively easy mistake to make. I beat my head against the wall for an hour or so on this one before intuition lead me to try putting a zero in front of it.

To Reproduce

Minimal pathological example based on docs.

package main

import (
        "fmt"
        "time"

        "github.com/go-co-op/gocron"
)

func task() {
        fmt.Println("I am running task.")
}

func taskWithParams(a int, b string) {
        fmt.Println(a, b)
}

func main() {
        // defines a new scheduler that schedules and runs jobs
        s1 := gocron.NewScheduler(time.UTC)

        // Do jobs with params
        s1.Every(1).Day().At("1:00").StartImmediately().Do(taskWithParams, 1, "hello")

        s1.StartBlocking()
}

Running this results in the following:

panic: reflect: call of reflect.Value.Type on zero Value

goroutine 5 [running]:
reflect.Value.Type(0x0, 0x0, 0x0, 0x0, 0x0)
        /Users/me/opt/go/src/reflect/value.go:1872 +0x183
github.com/go-co-op/gocron.callJobFuncWithParams(0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0)
        /Users/me/go/src/github.com/go-co-op/gocron/gocron.go:53 +0xc5
github.com/go-co-op/gocron.(*Job).run(0xc0000c4000)
        /Users/me/go/src/github.com/go-co-op/gocron/job.go:43 +0xd4
created by github.com/go-co-op/gocron.(*Scheduler).run
        /Users/me/go/src/github.com/go-co-op/gocron/scheduler.go:185 +0x167

Not particularly descriptive. :(

Steps to reproduce the behavior:

  1. go build
  2. run the above

Version

25952fd

Expected behavior

You should find a way to present a meaningful error message to the user here, even if you DO have to panic.

[FEATURE] - Remove Job from Scheduler Jobs when the Job is supposed to be launch only once.

Is your feature request related to a problem? Please describe

I've have a Job that i would like to run only once at a specific date

job, err = scheduler.Every(1).StartAt(date).SetTag(tag).Do(task)
...
job.LimitRunsTo(1)

However when the Job has run i still have a reference to it in the scheduler scheduler.Jobs()

I have to manually remove this specific Job in my task when the Job is done.

scheduler.RemoveJobByTag(tag)

Because if i don't, and if i run a second Job after this, then the second job will never be launched.

Describe the solution you'd like

When we choose to limit the job to be runned only once ( job.LimitRunsTo(1) ) and its execution has been done then remove this Job from the scheduler Jobs.

Add issue and pull request templates

Issues

  • Feature request template: What/Why/etc.
  • Bug report
  • A something else template

Pull Request

Should have: (my ideas for sections)

  • What does this do?
  • Which issue(s) does this relate to or fix?
  • Do these changes modify/break current functionality? If yes, how?
  • Have you included tests?
  • Have you included examples for new/changed functionality?

[FEATURE] - Persist schedule between runs

Hi,

Do I understand correctly that when the process stops, all scheduled jobs are lost?

I would like to save the jobs to disk, so they can be reloaded and scheduled again when my app is started.

I need to do this anyway, but I was wondering whether it makes sense that I create a pull request, so it can be added to the package?

Jo

How remove job?

Is your feature request related to a problem? Please describe

If we send job by api, how delete old job? delete by *task object is not work,

Describe the solution you'd like

I think we can add Id or tag ,for add job repeat,and can delete by tag/id

Describe alternatives you've considered

We can set job tag/id,and this can use this can find job,and handler job by this

Additional context

[FEATURE] - Add a way to use a custom time.Duration

Hello πŸ‘‹
I'd like to use a custom time.Duration. From what I can see in the code of this lib, the user is forced to set a unit, otherwise this error is returned:

// Error declarations for gocron related errors
var (
	// ...
	ErrPeriodNotSpecified  = errors.New("unspecified job period")
	// ...
)

func (j *Job) setPeriodDuration() error {
	interval := time.Duration(j.interval)

	switch j.unit {
	case seconds:
		j.periodDuration = interval * time.Second
	case minutes:
		j.periodDuration = interval * time.Minute
	case hours:
		j.periodDuration = interval * time.Hour
	case days:
		j.periodDuration = interval * time.Hour * 24
	case weeks:
		j.periodDuration = interval * time.Hour * 24 * 7
	default:
		return ErrPeriodNotSpecified
	}
	return nil
}

Simply removing that default case would allow users to do something like that (since time.Duration's underlying type is an int64):

j, err := scheduler.Every(uint64(mycustomduration)).Do(...)

Another solution would be to add a noop unit that doesn't multiply the value of the given duration, or add a way of directly use a time.Duration using another method (which would avoid the interval := time.Duration(j.interval) cast), something like sch.WithDuration or sch.EveryDuration, I'm havint trouble finding a good name to be honest πŸ˜„ )

[BUG] - Reflect panic with too many jobs possibly

Describe the bug

reported by @Algalish

panic: reflect: call of reflect.Value.Type on zero Value

goroutine 44934 [running]:
reflect.Value.Type(0x0, 0x0, 0x0, 0x0, 0x175fdd5aee8)
        /usr/local/go/src/reflect/value.go:1877 +0x166
github.com/go-co-op/gocron.callJobFuncWithParams(0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x175fe0375a8, 0x40fd526000000000, 0x0, 0x0)
       /projects/src/src/github.com/go-co-op/gocron/gocron.go:53 +0xc5
github.com/go-co-op/gocron.(*Job).run(0xc0000dc540)
        /projects/src/src/github.com/go-co-op/gocron/job.go:52 +0x13b
created by github.com/go-co-op/gocron.(*Scheduler).run
        /projects/src/src/github.com/go-co-op/gocron/scheduler.go:290 +0x3f

To Reproduce

cronScheduler = gocron.NewScheduler(time.UTC)
	for h := 0; h < 24; h++ {
		for m := 0; m < 60; m += 20 {
			timeString := fmt.Sprint(h) + ":" + fmt.Sprint(m)
			cronScheduler.Every(1).Day().At(timeString).Do(getModDetails)
			log.Println(timeString)
		}
	}

Version

Expected behavior

Additional context

Bug in scheduler

package main

import (
	"fmt"
	"time"

	"github.com/go-co-op/gocron"
)

func task() {
	fmt.Println(time.Now())
}

func main() {
	s1 := gocron.NewScheduler(time.UTC)
	s1.Every(10).Seconds().Do(task)
	<-s1.Start() // starts running (blocks current thread)
}

go run main.go
2020-03-31 23:10:10.5018339 -0500 CDT m=+10.003605201
2020-03-31 23:10:20.5028812 -0500 CDT m=+20.004652501
2020-03-31 23:10:30.501854 -0500 CDT m=+30.003625301
2020-03-31 23:10:31.5013486 -0500 CDT m=+31.003119901
2020-03-31 23:10:41.5022159 -0500 CDT m=+41.003987201
2020-03-31 23:10:51.5013717 -0500 CDT m=+51.003143001
2020-03-31 23:10:52.5031395 -0500 CDT m=+52.004910801
2020-03-31 23:11:02.5020695 -0500 CDT m=+62.003840801
2020-03-31 23:11:03.5014576 -0500 CDT m=+63.003228901
2020-03-31 23:11:13.5016872 -0500 CDT m=+73.003458501
2020-03-31 23:11:23.5017684 -0500 CDT m=+83.003539701
2020-03-31 23:11:33.5013161 -0500 CDT m=+93.003087401
2020-03-31 23:11:34.508206 -0500 CDT m=+94.009977301
2020-03-31 23:11:44.5021093 -0500 CDT m=+104.003880601
2020-03-31 23:11:45.503181 -0500 CDT m=+105.004952301
2020-03-31 23:11:55.5017912 -0500 CDT m=+115.003562501
2020-03-31 23:11:56.5028593 -0500 CDT m=+116.004630601
2020-03-31 23:12:06.5013873 -0500 CDT m=+126.003158601
2020-03-31 23:12:07.5014535 -0500 CDT m=+127.003224801
2020-03-31 23:12:17.5013532 -0500 CDT m=+137.003124501
2020-03-31 23:12:18.5029237 -0500 CDT m=+138.004695001
2020-03-31 23:12:28.501378 -0500 CDT m=+148.003149301
2020-03-31 23:12:29.5012798 -0500 CDT m=+149.003051101
2020-03-31 23:12:39.5022707 -0500 CDT m=+159.004042001
2020-03-31 23:12:49.5018604 -0500 CDT m=+169.003631701
2020-03-31 23:12:50.5017803 -0500 CDT m=+170.003551601
2020-03-31 23:13:00.5019564 -0500 CDT m=+180.003727701
2020-03-31 23:13:10.5020875 -0500 CDT m=+190.003858801
2020-03-31 23:13:20.5016438 -0500 CDT m=+200.003415101
2020-03-31 23:13:21.5017742 -0500 CDT m=+201.003545501
2020-03-31 23:13:31.503144 -0500 CDT m=+211.004915301
2020-03-31 23:13:41.5014638 -0500 CDT m=+221.003235101
2020-03-31 23:13:42.5028217 -0500 CDT m=+222.004593001
2020-03-31 23:13:52.5013111 -0500 CDT m=+232.003082401
2020-03-31 23:13:53.5014415 -0500 CDT m=+233.003212801
2020-03-31 23:14:03.501239 -0500 CDT m=+243.003010301
2020-03-31 23:14:04.5013094 -0500 CDT m=+244.003080701
2020-03-31 23:14:14.5030847 -0500 CDT m=+254.004856001
2020-03-31 23:14:24.5019311 -0500 CDT m=+264.003702401
2020-03-31 23:14:25.5020085 -0500 CDT m=+265.003779801

release-v0.1.1 doesn't have StartBlocking

when I use the release version of V0.1.1 to do some cronjob test, I found that it doesn't have the function StartBlocking. I have to use <- xxx.Start() as an instead.
Is it be added in recent days? or what's the plan to add it to a new release version?

[FEATURE] - Monthly Schedule

I could not find a way to schedule cron job every month on specific day (Jan 5, Feb 5, March 5 so on).

gocron.Every(1).Month().On(5).At("hh:mm")

Is that already possible now or should be requested as a new feature?

Thanks

[BUG] - switch from daylight saving time to standard time causes problem

Describe the bug

I am running a job on 00:01 every day. Today was the switch from daylight saving time to standard time in Europe. The job did run one hour too early at 23:01.
Update: And I just realized the job was executed twice.

To Reproduce

_, err := s.Every(1).Day().At("00:01").Do(reports.RunDailyReport)

Version

0.3.1

Expected behavior

It should not run at 23:01. It should only run once.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.