Repository: go-co-op/gocron (Go)

Issue:
[FEATURE] Add support of the alias name for job
### Is your feature request related to a problem? Please describe
Currently, the executor prevents the same job from being run more than once by the function name, and need to create many different functions if we wanna create a cron job per user. So I'm wondering if it's possible to allow users to set the alias name for the job.


### Describe the solution you'd like

Add a new API like `DoWithName()` to create a job with the customized name, and use this name as the distributed locking key.

I will submit the PR if this proposal sounds good to you. @JohnRoesler 




Discussion: How about instead of a new Do method, add another Job method that can be called in the scheduler chain to ‘SetJobName’. If no name is set, default to the function name as is currently used. 
Sure, thanks for your suggestion. After revising the current implementation, I have two questions:

1. The current job name is good to know which function is running, so I'm wondering if we can add another field like `job.Key` or `job.UniqueKey` for this case?
2. The job name needs to be set before scheduling, so we need to set the job name before the Do function. And if we do this with `scheduler.Do().SetJobName`, the job maybe run before setting job name.
>  1. The current job name is good to know which function is running, so I'm wondering if we can add another field like job.Key or job.UniqueKey for this case?

I agree. Let's add a field to  the `Job` that is `Name string`.

> 2. The job name needs to be set before scheduling, so we need to set the job name before the Do function. And if we do this with scheduler.Do().SetJobName, the job maybe run before setting job name.

`Do()` returns the `Job` and an `error`, so it cannot be called in the scheduling chain like you have listed in the example `scheduler.Do().SetJobName`

Within the new `func (s *Scheduler) SetJobName() *Scheduler` function, make sure that you call `getCurrentJob()` to get the job like other functions do.
Thanks for your detail explain.



Fix (patch):
diff --git a/executor.go b/executor.go
index 31e9ecd5..aa16b84e 100644
--- a/executor.go
+++ b/executor.go
@@ -132,8 +132,12 @@ func (e *executor) start() {
 func (e *executor) runJob(f jobFunction) {
 	switch f.runConfig.mode {
 	case defaultMode:
+		lockKey := f.jobName
+		if lockKey == "" {
+			lockKey = f.funcName
+		}
 		if e.distributedLocker != nil {
-			l, err := e.distributedLocker.Lock(f.ctx, f.name)
+			l, err := e.distributedLocker.Lock(f.ctx, lockKey)
 			if err != nil || l == nil {
 				return
 			}
@@ -190,7 +194,7 @@ func (e *executor) run() {
 				if panicHandler != nil {
 					defer func() {
 						if r := recover(); r != any(nil) {
-							panicHandler(f.name, r)
+							panicHandler(f.funcName, r)
 						}
 					}()
 				}
diff --git a/gocron.go b/gocron.go
index 58931ea7..ec97f45a 100644
--- a/gocron.go
+++ b/gocron.go
@@ -28,7 +28,7 @@ var (
 // SetPanicHandler sets the global panicHandler to the given function.
 // Leaving it nil or setting it to nil disables automatic panic handling.
 // If the panicHandler is not nil, any panic that occurs during executing a job will be recovered
-// and the panicHandlerFunc will be called with the job's name and the recover data.
+// and the panicHandlerFunc will be called with the job's funcName and the recover data.
 func SetPanicHandler(handler PanicHandlerFunc) {
 	panicHandlerMutex.Lock()
 	defer panicHandlerMutex.Unlock()
diff --git a/job.go b/job.go
index 653d3918..74a23811 100644
--- a/job.go
+++ b/job.go
@@ -45,7 +45,8 @@ type jobFunction struct {
 	function          any                // task's function
 	parameters        []any              // task's function parameters
 	parametersLen     int                // length of the passed parameters
-	name              string             // nolint the function name to run
+	jobName           string             // key of the distributed lock
+	funcName          string             // the name of the function - e.g. main.func1
 	runConfig         runConfig          // configuration for how many times to run the job
 	singletonQueue    chan struct{}      // queues jobs for the singleton runner to handle
 	singletonRunnerOn *atomic.Bool       // whether the runner function for singleton is running
@@ -74,7 +75,8 @@ func (jf *jobFunction) copy() jobFunction {
 		function:          jf.function,
 		parameters:        nil,
 		parametersLen:     jf.parametersLen,
-		name:              jf.name,
+		funcName:          jf.funcName,
+		jobName:           jf.jobName,
 		runConfig:         jf.runConfig,
 		singletonQueue:    jf.singletonQueue,
 		ctx:               jf.ctx,
@@ -135,6 +137,16 @@ func newJob(interval int, startImmediately bool, singletonMode bool) *Job {
 	return job
 }
 
+// Name sets the name of the current job.
+//
+// If the scheduler is running using WithDistributedLocker(),
+// the job name is used as the distributed lock key.
+func (j *Job) Name(name string) {
+	j.mu.Lock()
+	defer j.mu.Unlock()
+	j.jobName = name
+}
+
 func (j *Job) setRandomInterval(a, b int) {
 	j.random.rand = rand.New(rand.NewSource(time.Now().UnixNano())) // nolint
 
diff --git a/scheduler.go b/scheduler.go
index 36498b65..454d3de0 100644
--- a/scheduler.go
+++ b/scheduler.go
@@ -126,6 +126,16 @@ func (s *Scheduler) Jobs() []*Job {
 	return s.jobs
 }
 
+// Name sets the name of the current job.
+//
+// If the scheduler is running using WithDistributedLocker(), the job name is used
+// as the distributed lock key.
+func (s *Scheduler) Name(name string) *Scheduler {
+	job := s.getCurrentJob()
+	job.jobName = name
+	return s
+}
+
 func (s *Scheduler) setJobs(jobs []*Job) {
 	s.jobsMutex.Lock()
 	defer s.jobsMutex.Unlock()
@@ -658,7 +668,7 @@ func (s *Scheduler) Remove(job any) {
 	j := s.findJobByTaskName(fName)
 	s.removeJobsUniqueTags(j)
 	s.removeByCondition(func(someJob *Job) bool {
-		return someJob.name == fName
+		return someJob.funcName == fName
 	})
 }
 
@@ -674,7 +684,7 @@ func (s *Scheduler) RemoveByReference(job *Job) {
 
 func (s *Scheduler) findJobByTaskName(name string) *Job {
 	for _, job := range s.Jobs() {
-		if job.name == name {
+		if job.funcName == name {
 			return job
 		}
 	}
@@ -820,7 +830,7 @@ func (s *Scheduler) SingletonModeAll() {
 // TaskPresent checks if specific job's function was added to the scheduler.
 func (s *Scheduler) TaskPresent(j any) bool {
 	for _, job := range s.Jobs() {
-		if job.name == getFunctionName(j) {
+		if job.funcName == getFunctionName(j) {
 			return true
 		}
 	}
@@ -915,10 +925,10 @@ func (s *Scheduler) doCommon(jobFun any, params ...any) (*Job, error) {
 	}
 
 	fname := getFunctionName(jobFun)
-	if job.name != fname {
+	if job.funcName != fname {
 		job.function = jobFun
 		job.parameters = params
-		job.name = fname
+		job.funcName = fname
 	}
 
 	f := reflect.ValueOf(jobFun)