Files
Atay-Makhzan/modules/indexer/code/indexer.go
T

314 lines
9.5 KiB
Go
Raw Normal View History

2017-01-24 21:43:02 -05:00
// Copyright 2016 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
2017-01-24 21:43:02 -05:00
package code
2017-01-24 21:43:02 -05:00
import (
2019-12-24 07:26:34 +00:00
"context"
"os"
"runtime/pprof"
"slices"
2023-06-23 20:37:56 +08:00
"sync/atomic"
2019-12-23 20:31:16 +08:00
"time"
2017-09-16 13:16:21 -07:00
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
2019-12-23 20:31:16 +08:00
"code.gitea.io/gitea/modules/graceful"
2023-06-23 20:37:56 +08:00
"code.gitea.io/gitea/modules/indexer/code/bleve"
"code.gitea.io/gitea/modules/indexer/code/elasticsearch"
"code.gitea.io/gitea/modules/indexer/code/internal"
2019-12-23 20:31:16 +08:00
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/process"
2020-09-07 23:05:08 +08:00
"code.gitea.io/gitea/modules/queue"
2018-02-05 10:29:17 -08:00
"code.gitea.io/gitea/modules/setting"
2019-12-23 20:31:16 +08:00
)
2018-02-05 10:29:17 -08:00
2023-06-23 20:37:56 +08:00
var (
indexerQueue *queue.WorkerPoolQueue[*internal.IndexerData]
// globalIndexer is the global indexer, it cannot be nil.
// When the real indexer is not ready, it will be a dummy indexer which will return error to explain it's not ready.
// So it's always safe use it as *globalIndexer.Load() and call its methods.
globalIndexer atomic.Pointer[internal.Indexer]
dummyIndexer *internal.Indexer
)
2023-06-23 20:37:56 +08:00
func init() {
i := internal.NewDummyIndexer()
dummyIndexer = &i
globalIndexer.Store(dummyIndexer)
2020-09-07 23:05:08 +08:00
}
2023-06-23 20:37:56 +08:00
func index(ctx context.Context, indexer internal.Indexer, repoID int64) error {
repo, err := repo_model.GetRepositoryByID(ctx, repoID)
if repo_model.IsErrRepoNotExist(err) {
2023-06-23 20:37:56 +08:00
return indexer.Delete(ctx, repoID)
}
2020-09-07 23:05:08 +08:00
if err != nil {
return err
}
repoTypes := setting.Indexer.RepoIndexerRepoTypes
if len(repoTypes) == 0 {
repoTypes = []string{"sources"}
}
// skip forks from being indexed if unit is not present
if !slices.Contains(repoTypes, "forks") && repo.IsFork {
return nil
}
// skip mirrors from being indexed if unit is not present
if !slices.Contains(repoTypes, "mirrors") && repo.IsMirror {
return nil
}
// skip templates from being indexed if unit is not present
if !slices.Contains(repoTypes, "templates") && repo.IsTemplate {
return nil
}
// skip regular repos from being indexed if unit is not present
if !slices.Contains(repoTypes, "sources") && !repo.IsFork && !repo.IsMirror && !repo.IsTemplate {
return nil
}
sha, err := getDefaultBranchSha(ctx, repo)
2020-09-07 23:05:08 +08:00
if err != nil {
return err
}
changes, err := getRepoChanges(ctx, repo, sha)
2020-09-07 23:05:08 +08:00
if err != nil {
return err
} else if changes == nil {
return nil
}
if err := indexer.Index(ctx, repo, sha, changes); err != nil {
2020-09-07 23:05:08 +08:00
return err
}
return repo_model.UpdateIndexerStatus(ctx, repo, repo_model.RepoIndexerTypeCode, sha)
2020-09-07 23:05:08 +08:00
}
2019-12-23 20:31:16 +08:00
// Init initialize the repo indexer
func Init() {
if !setting.Indexer.RepoIndexerEnabled {
2023-06-23 20:37:56 +08:00
(*globalIndexer.Load()).Close()
2019-12-23 20:31:16 +08:00
return
}
2017-09-24 17:08:48 -07:00
ctx, cancel, finished := process.GetManager().AddTypedContext(context.Background(), "Service: CodeIndexer", process.SystemProcessType, false)
2019-12-24 07:26:34 +00:00
graceful.GetManager().RunAtTerminate(func() {
select {
case <-ctx.Done():
return
default:
}
cancel()
2019-12-24 07:26:34 +00:00
log.Debug("Closing repository indexer")
2023-06-23 20:37:56 +08:00
(*globalIndexer.Load()).Close()
2019-12-24 07:26:34 +00:00
log.Info("PID: %d Repository Indexer closed", os.Getpid())
finished()
2019-12-24 07:26:34 +00:00
})
waitChannel := make(chan time.Duration, 1)
2020-09-07 23:05:08 +08:00
// Create the Queue
switch setting.Indexer.RepoType {
case "bleve", "elasticsearch":
2023-06-23 20:37:56 +08:00
handler := func(items ...*internal.IndexerData) (unhandled []*internal.IndexerData) {
indexer := *globalIndexer.Load()
2023-05-08 19:49:59 +08:00
for _, indexerData := range items {
log.Trace("IndexerData Process Repo: %d", indexerData.RepoID)
if err := index(ctx, indexer, indexerData.RepoID); err != nil {
2023-06-23 20:37:56 +08:00
unhandled = append(unhandled, indexerData)
2023-05-08 19:49:59 +08:00
if !setting.IsInTesting {
log.Error("Codes indexer handler: index error for repo %v: %v", indexerData.RepoID, err)
}
2020-09-07 23:05:08 +08:00
}
}
return unhandled
2020-09-07 23:05:08 +08:00
}
2023-05-26 15:31:55 +08:00
indexerQueue = queue.CreateUniqueQueue(ctx, "code_indexer", handler)
2020-09-07 23:05:08 +08:00
if indexerQueue == nil {
log.Fatal("Unable to create codes indexer queue")
}
default:
log.Fatal("Unknown codes indexer type; %s", setting.Indexer.RepoType)
}
2019-12-23 20:31:16 +08:00
go func() {
pprof.SetGoroutineLabels(ctx)
2019-12-23 20:31:16 +08:00
start := time.Now()
var (
2023-06-23 20:37:56 +08:00
rIndexer internal.Indexer
existed bool
err error
)
switch setting.Indexer.RepoType {
case "bleve":
log.Info("PID: %d Initializing Repository Indexer at: %s", os.Getpid(), setting.Indexer.RepoPath)
defer func() {
if err := recover(); err != nil {
log.Error("PANIC whilst initializing repository indexer: %v\nStacktrace: %s", err, log.Stack(2))
log.Error("The indexer files are likely corrupted and may need to be deleted")
log.Error("You can completely remove the \"%s\" directory to make Gitea recreate the indexes", setting.Indexer.RepoPath)
}
}()
2023-06-23 20:37:56 +08:00
rIndexer = bleve.NewIndexer(setting.Indexer.RepoPath)
existed, err = rIndexer.Init(ctx)
if err != nil {
cancel()
2023-06-23 20:37:56 +08:00
(*globalIndexer.Load()).Close()
close(waitChannel)
log.Fatal("PID: %d Unable to initialize the bleve Repository Indexer at path: %s Error: %v", os.Getpid(), setting.Indexer.RepoPath, err)
}
case "elasticsearch":
log.Info("PID: %d Initializing Repository Indexer at: %s", os.Getpid(), setting.Indexer.RepoConnStr)
defer func() {
if err := recover(); err != nil {
log.Error("PANIC whilst initializing repository indexer: %v\nStacktrace: %s", err, log.Stack(2))
log.Error("The indexer files are likely corrupted and may need to be deleted")
log.Error("You can completely remove the \"%s\" index to make Gitea recreate the indexes", setting.Indexer.RepoConnStr)
}
}()
2023-06-23 20:37:56 +08:00
rIndexer = elasticsearch.NewIndexer(setting.Indexer.RepoConnStr, setting.Indexer.RepoIndexerName)
if err != nil {
cancel()
(*globalIndexer.Load()).Close()
close(waitChannel)
log.Fatal("PID: %d Unable to create the elasticsearch Repository Indexer connstr: %s Error: %v", os.Getpid(), setting.Indexer.RepoConnStr, err)
}
existed, err = rIndexer.Init(ctx)
if err != nil {
cancel()
2023-06-23 20:37:56 +08:00
(*globalIndexer.Load()).Close()
close(waitChannel)
log.Fatal("PID: %d Unable to initialize the elasticsearch Repository Indexer connstr: %s Error: %v", os.Getpid(), setting.Indexer.RepoConnStr, err)
2019-12-24 07:26:34 +00:00
}
2023-06-23 20:37:56 +08:00
default:
log.Fatal("PID: %d Unknown Indexer type: %s", os.Getpid(), setting.Indexer.RepoType)
2019-12-23 20:31:16 +08:00
}
2023-06-23 20:37:56 +08:00
globalIndexer.Store(&rIndexer)
2017-09-24 17:08:48 -07:00
2020-09-07 23:05:08 +08:00
// Start processing the queue
2023-05-26 15:31:55 +08:00
go graceful.GetManager().RunWithCancel(indexerQueue)
2017-09-24 17:08:48 -07:00
2023-06-23 20:37:56 +08:00
if !existed { // populate the index because it's created for the first time
2020-09-07 23:05:08 +08:00
go graceful.GetManager().RunWithShutdownContext(populateRepoIndexer)
2019-12-23 20:31:16 +08:00
}
2019-12-24 07:26:34 +00:00
select {
case waitChannel <- time.Since(start):
case <-graceful.GetManager().IsShutdown():
}
2017-09-24 17:08:48 -07:00
2019-12-24 07:26:34 +00:00
close(waitChannel)
2019-12-23 20:31:16 +08:00
}()
2017-09-24 17:08:48 -07:00
2019-12-23 20:31:16 +08:00
if setting.Indexer.StartupTimeout > 0 {
go func() {
pprof.SetGoroutineLabels(ctx)
2019-12-23 20:31:16 +08:00
timeout := setting.Indexer.StartupTimeout
if graceful.GetManager().IsChild() && setting.GracefulHammerTime > 0 {
timeout += setting.GracefulHammerTime
}
select {
2019-12-24 07:26:34 +00:00
case <-graceful.GetManager().IsShutdown():
log.Warn("Shutdown before Repository Indexer completed initialization")
cancel()
2023-06-23 20:37:56 +08:00
(*globalIndexer.Load()).Close()
2019-12-24 07:26:34 +00:00
case duration, ok := <-waitChannel:
if !ok {
log.Warn("Repository Indexer Initialization failed")
cancel()
2023-06-23 20:37:56 +08:00
(*globalIndexer.Load()).Close()
2019-12-24 07:26:34 +00:00
return
}
2019-12-23 20:31:16 +08:00
log.Info("Repository Indexer Initialization took %v", duration)
case <-time.After(timeout):
2019-12-24 07:26:34 +00:00
cancel()
2023-06-23 20:37:56 +08:00
(*globalIndexer.Load()).Close()
2019-12-23 20:31:16 +08:00
log.Fatal("Repository Indexer Initialization Timed-Out after: %v", timeout)
}
}()
2017-09-24 17:08:48 -07:00
}
}
2020-09-07 23:05:08 +08:00
// UpdateRepoIndexer update a repository's entries in the indexer
func UpdateRepoIndexer(repo *repo_model.Repository) {
2023-06-23 20:37:56 +08:00
indexData := &internal.IndexerData{RepoID: repo.ID}
2020-09-07 23:05:08 +08:00
if err := indexerQueue.Push(indexData); err != nil {
log.Error("Update repo index data %v failed: %v", indexData, err)
}
}
// IsAvailable checks if issue indexer is available
2023-06-23 20:37:56 +08:00
func IsAvailable(ctx context.Context) bool {
return (*globalIndexer.Load()).Ping(ctx) == nil
}
2020-09-07 23:05:08 +08:00
// populateRepoIndexer populate the repo indexer with pre-existing data. This
// should only be run when the indexer is created for the first time.
func populateRepoIndexer(ctx context.Context) {
log.Info("Populating the repo indexer with existing repositories")
exist, err := db.IsTableNotEmpty("repository")
2020-09-07 23:05:08 +08:00
if err != nil {
log.Fatal("System error: %v", err)
} else if !exist {
return
}
// if there is any existing repo indexer metadata in the DB, delete it
// since we are starting afresh. Also, xorm requires deletes to have a
// condition, and we want to delete everything, thus 1=1.
if err := db.DeleteAllRecords("repo_indexer_status"); err != nil {
2020-09-07 23:05:08 +08:00
log.Fatal("System error: %v", err)
}
var maxRepoID int64
if maxRepoID, err = db.GetMaxID("repository"); err != nil {
2020-09-07 23:05:08 +08:00
log.Fatal("System error: %v", err)
}
// start with the maximum existing repo ID and work backwards, so that we
// don't include repos that are created after gitea starts; such repos will
// already be added to the indexer, and we don't need to add them again.
for maxRepoID > 0 {
select {
case <-ctx.Done():
log.Info("Repository Indexer population shutdown before completion")
return
default:
}
ids, err := repo_model.GetUnindexedRepos(repo_model.RepoIndexerTypeCode, maxRepoID, 0, 50)
2020-09-07 23:05:08 +08:00
if err != nil {
log.Error("populateRepoIndexer: %v", err)
return
} else if len(ids) == 0 {
break
}
for _, id := range ids {
select {
case <-ctx.Done():
log.Info("Repository Indexer population shutdown before completion")
return
default:
}
2023-06-23 20:37:56 +08:00
if err := indexerQueue.Push(&internal.IndexerData{RepoID: id}); err != nil {
2020-09-07 23:05:08 +08:00
log.Error("indexerQueue.Push: %v", err)
return
}
maxRepoID = id - 1
}
}
log.Info("Done (re)populating the repo indexer with existing repositories")
}