You are an expert in writing code in many different languages. Your goal is to perform code completion for the following code, keeping in mind the rest of the code and the file meta data.
    Metadata details:
    filename: reaper.go
    path of file: ./repos/harbor/src/jobservice/worker/cworker
    
    the code of the file until where you have to start completion: // Copyright Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cworker

import (
	"context"
	"fmt"
	"strconv"
	"time"

	"github.com/gomodule/redigo/redis"

	"github.com/goharbor/harbor/src/jobservice/common/rds"
	"github.com/goharbor/harbor/src/jobservice/config"
	"github.com/goharbor/harbor/src/jobservice/errs"
	"github.com/goharbor/harbor/src/jobservice/job"
	"github.com/goharbor/harbor/src/jobservice/lcm"
	"github.com/goharbor/harbor/src/jobservice/logger"
	"github.com/goharbor/harbor/src/lib/errors"
)

const (
	reapLoopInterval        = 1 * time.Hour
	initialReapLoopInterval = 5 * time.Minute
)

// reaper is designed to reap the outdated job stats and web hook
type reaper struct {
	context   context.Context
	namespace string
	pool      *redis.Pool
	lcmCtl    lcm.Controller
	jobTypes  []string
}

// start the reap process
// Non blocking call
func (r *reaper) start() {
	// Start the interval job stats sync loop
	go func() {
		defer logger.Info("Reaper is stopped")

		tm := time.NewTimer(initialReapLoopInterval)
		defer tm.Stop()

		logger.Info("Reaper is started")
		for {
			select {
			case <-tm.C:
				tm.Reset(reapLoopInterval)
				if err := r.syncOutdatedStats(); err != nil {
					// Just log
					logger.Error(err)
				}
			case <-r.context.Done():
				return // Terminated
			}
		}
	}()

	// Start re-enqueue in-progress jobs process.
	// Only run once at the start point.
	go func() {
		// Wait for a short while and then start
		<-time.After(5 * time.Second)
		if err := r.reEnqueueInProgressJobs();
    
func (r *reaper) syncOutdatedStats() error {
	// Debug
	logger.Info("Start: reap outdated job stats")
	defer logger.Info("End: reap outdated job stats")

	// Loop all the in progress jobs to check if they're really in progress or
	// status is hung.
	h := func(k string, v int64) (err error) {
		defer func() {
			if errs.IsObjectNotFoundError(err) {
				// As the job stats is lost and we don't have chance to restore it, then directly discard it.
				// Un-track the in-progress record
				if e := r.unTrackInProgress(k); e != nil {
					// Wrap error
					err = errors.Wrap(e, err.Error())
				}
			}

			if err != nil {
				err = errors.Wrap(err, "sync outdated stats handler error")
			}
		}()

		var t job.Tracker
		t, err = r.lcmCtl.Track(k)
		if err != nil {
			return
		}

		// Compare and check if the status and the ACKed status are consistent
		diff := compare(t.Job().Info)
		if diff == 0 {
			// Status and ACKed status are consistent
			if job.Status(t.Job().Info.Status).Final() {
				// Final status
				// The inprogress track record is not valid as everything is done and consistent.
				// It should not happen. However, if it is really happened, we can fix it here.
				if err = r.unTrackInProgress(t.Job().Info.JobID); err != nil {
					return
				}
			} else {
				// Ongoing, check the update timestamp to make sure it is not hung
				if time.Unix(t.Job().Info.UpdateTime, 0).Add(config.MaxUpdateDuration()).Before(time.Now()) {
					// Status hung
					// Mark job status to error state
					if err = t.Fail(); err != nil {
						return
					}

					// Log and exit
					logger.Infof("Reaper: mark job %s failed as job is still not finished in 1 day", t.Job().Info.JobID)
				}

				// Exit as it is still a valid ongoing job
			}
		} else if diff > 0 {
			// The hook event of current job status is not ACKed
			// Resend hook event by set the status again
			if err = t.FireHook(); err != nil {
				return
			}

			// Success and exit
			logger.Infof(
				"Reaper: fire hook again for job %s as job status change is not ACKed: %s(rev=%d)",
				t.Job().Info.JobID,
				t.Job().Info.Status,
				t.Job().Info.Revision,
			)
		} else {
			// Current status is outdated, update it with ACKed status.
			if err = t.UpdateStatusWithRetry(job.Status(t.Job().Info.HookAck.Status)); err != nil {
				return
			}

			// Success and exit
			logger.Infof(
				"Reaper: update the status of job %s to the ACKed status: %s(%d)",
				t.Job().Info.JobID,
				t.Job().Info.HookAck.Status,
				t.Job().Info.Revision,
			)
		}

		return nil
	}

	if err := r.scanLocks(rds.KeyJobTrackInProgress(r.namespace), h); err != nil {
		return errors.Wrap(err, "reaper error")
	}

	return nil
}