2018-10-26 15:48:22 +02:00
|
|
|
// Manage background jobs that the rc is running
|
|
|
|
|
|
|
|
package rc
|
|
|
|
|
|
|
|
import (
|
2019-06-17 10:34:30 +02:00
|
|
|
"context"
|
2018-10-26 15:48:22 +02:00
|
|
|
"sync"
|
|
|
|
"sync/atomic"
|
|
|
|
"time"
|
|
|
|
|
2019-06-13 14:04:59 +02:00
|
|
|
"github.com/ncw/rclone/fs"
|
2018-10-26 15:48:22 +02:00
|
|
|
"github.com/pkg/errors"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Job describes a asynchronous task started via the rc package
|
|
|
|
type Job struct {
|
|
|
|
mu sync.Mutex
|
2019-06-17 10:34:30 +02:00
|
|
|
ID int64 `json:"id"`
|
|
|
|
StartTime time.Time `json:"startTime"`
|
|
|
|
EndTime time.Time `json:"endTime"`
|
|
|
|
Error string `json:"error"`
|
|
|
|
Finished bool `json:"finished"`
|
|
|
|
Success bool `json:"success"`
|
|
|
|
Duration float64 `json:"duration"`
|
|
|
|
Output Params `json:"output"`
|
|
|
|
Context context.Context `json:"-"`
|
2018-10-26 15:48:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Jobs describes a collection of running tasks
|
|
|
|
type Jobs struct {
|
|
|
|
mu sync.RWMutex
|
|
|
|
jobs map[int64]*Job
|
|
|
|
expireInterval time.Duration
|
|
|
|
expireRunning bool
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
running = newJobs()
|
|
|
|
jobID = int64(0)
|
|
|
|
)
|
|
|
|
|
|
|
|
// newJobs makes a new Jobs structure
|
|
|
|
func newJobs() *Jobs {
|
|
|
|
return &Jobs{
|
|
|
|
jobs: map[int64]*Job{},
|
2019-06-13 14:04:59 +02:00
|
|
|
expireInterval: fs.Config.RcJobExpireInterval,
|
2018-10-26 15:48:22 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// kickExpire makes sure Expire is running
|
|
|
|
func (jobs *Jobs) kickExpire() {
|
|
|
|
jobs.mu.Lock()
|
|
|
|
defer jobs.mu.Unlock()
|
|
|
|
if !jobs.expireRunning {
|
|
|
|
time.AfterFunc(jobs.expireInterval, jobs.Expire)
|
|
|
|
jobs.expireRunning = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Expire expires any jobs that haven't been collected
|
|
|
|
func (jobs *Jobs) Expire() {
|
|
|
|
jobs.mu.Lock()
|
|
|
|
defer jobs.mu.Unlock()
|
|
|
|
now := time.Now()
|
|
|
|
for ID, job := range jobs.jobs {
|
|
|
|
job.mu.Lock()
|
2019-06-13 14:04:59 +02:00
|
|
|
if job.Finished && now.Sub(job.EndTime) > fs.Config.RcJobExpireDuration {
|
2018-10-26 15:48:22 +02:00
|
|
|
delete(jobs.jobs, ID)
|
|
|
|
}
|
|
|
|
job.mu.Unlock()
|
|
|
|
}
|
|
|
|
if len(jobs.jobs) != 0 {
|
|
|
|
time.AfterFunc(jobs.expireInterval, jobs.Expire)
|
|
|
|
jobs.expireRunning = true
|
|
|
|
} else {
|
|
|
|
jobs.expireRunning = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// IDs returns the IDs of the running jobs
|
|
|
|
func (jobs *Jobs) IDs() (IDs []int64) {
|
|
|
|
jobs.mu.RLock()
|
|
|
|
defer jobs.mu.RUnlock()
|
|
|
|
IDs = []int64{}
|
|
|
|
for ID := range jobs.jobs {
|
|
|
|
IDs = append(IDs, ID)
|
|
|
|
}
|
|
|
|
return IDs
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get a job with a given ID or nil if it doesn't exist
|
|
|
|
func (jobs *Jobs) Get(ID int64) *Job {
|
|
|
|
jobs.mu.RLock()
|
|
|
|
defer jobs.mu.RUnlock()
|
|
|
|
return jobs.jobs[ID]
|
|
|
|
}
|
|
|
|
|
|
|
|
// mark the job as finished
|
|
|
|
func (job *Job) finish(out Params, err error) {
|
|
|
|
job.mu.Lock()
|
|
|
|
job.EndTime = time.Now()
|
|
|
|
if out == nil {
|
|
|
|
out = make(Params)
|
|
|
|
}
|
|
|
|
job.Output = out
|
|
|
|
job.Duration = job.EndTime.Sub(job.StartTime).Seconds()
|
|
|
|
if err != nil {
|
|
|
|
job.Error = err.Error()
|
|
|
|
job.Success = false
|
|
|
|
} else {
|
|
|
|
job.Error = ""
|
|
|
|
job.Success = true
|
|
|
|
}
|
|
|
|
job.Finished = true
|
|
|
|
job.mu.Unlock()
|
|
|
|
running.kickExpire() // make sure this job gets expired
|
|
|
|
}
|
|
|
|
|
|
|
|
// run the job until completion writing the return status
|
|
|
|
func (job *Job) run(fn Func, in Params) {
|
|
|
|
defer func() {
|
|
|
|
if r := recover(); r != nil {
|
|
|
|
job.finish(nil, errors.Errorf("panic received: %v", r))
|
|
|
|
}
|
|
|
|
}()
|
2019-06-17 10:34:30 +02:00
|
|
|
job.finish(fn(job.Context, in))
|
2018-10-26 15:48:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewJob start a new Job off
|
|
|
|
func (jobs *Jobs) NewJob(fn Func, in Params) *Job {
|
|
|
|
job := &Job{
|
|
|
|
ID: atomic.AddInt64(&jobID, 1),
|
|
|
|
StartTime: time.Now(),
|
2019-06-17 10:34:30 +02:00
|
|
|
Context: context.Background(),
|
2018-10-26 15:48:22 +02:00
|
|
|
}
|
|
|
|
go job.run(fn, in)
|
|
|
|
jobs.mu.Lock()
|
|
|
|
jobs.jobs[job.ID] = job
|
|
|
|
jobs.mu.Unlock()
|
|
|
|
return job
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// StartJob starts a new job and returns a Param suitable for output
|
|
|
|
func StartJob(fn Func, in Params) (Params, error) {
|
|
|
|
job := running.NewJob(fn, in)
|
|
|
|
out := make(Params)
|
|
|
|
out["jobid"] = job.ID
|
|
|
|
return out, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
Add(Call{
|
|
|
|
Path: "job/status",
|
|
|
|
Fn: rcJobStatus,
|
|
|
|
Title: "Reads the status of the job ID",
|
|
|
|
Help: `Parameters
|
|
|
|
- jobid - id of the job (integer)
|
|
|
|
|
|
|
|
Results
|
|
|
|
- finished - boolean
|
|
|
|
- duration - time in seconds that the job ran for
|
|
|
|
- endTime - time the job finished (eg "2018-10-26T18:50:20.528746884+01:00")
|
|
|
|
- error - error from the job or empty string for no error
|
|
|
|
- finished - boolean whether the job has finished or not
|
|
|
|
- id - as passed in above
|
|
|
|
- startTime - time the job started (eg "2018-10-26T18:50:20.528336039+01:00")
|
|
|
|
- success - boolean - true for success false otherwise
|
|
|
|
- output - output of the job as would have been returned if called synchronously
|
2019-06-17 10:34:30 +02:00
|
|
|
- progress - output of the progress related to the underlying job
|
2018-10-26 15:48:22 +02:00
|
|
|
`,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns the status of a job
|
2019-06-17 10:34:30 +02:00
|
|
|
func rcJobStatus(ctx context.Context, in Params) (out Params, err error) {
|
2018-10-26 15:48:22 +02:00
|
|
|
jobID, err := in.GetInt64("jobid")
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
job := running.Get(jobID)
|
|
|
|
if job == nil {
|
|
|
|
return nil, errors.New("job not found")
|
|
|
|
}
|
|
|
|
job.mu.Lock()
|
|
|
|
defer job.mu.Unlock()
|
|
|
|
out = make(Params)
|
|
|
|
err = Reshape(&out, job)
|
2019-03-18 12:23:00 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "reshape failed in job status")
|
2018-10-26 15:48:22 +02:00
|
|
|
}
|
|
|
|
return out, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
Add(Call{
|
|
|
|
Path: "job/list",
|
|
|
|
Fn: rcJobList,
|
|
|
|
Title: "Lists the IDs of the running jobs",
|
|
|
|
Help: `Parameters - None
|
|
|
|
|
|
|
|
Results
|
|
|
|
- jobids - array of integer job ids
|
|
|
|
`,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns the status of a job
|
2019-06-17 10:34:30 +02:00
|
|
|
func rcJobList(ctx context.Context, in Params) (out Params, err error) {
|
2018-10-26 15:48:22 +02:00
|
|
|
out = make(Params)
|
|
|
|
out["jobids"] = running.IDs()
|
|
|
|
return out, nil
|
|
|
|
}
|