本站源代码
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

276 lines
6.7KB

  1. // Copyright 2018 The Gitea Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package issues
  5. import (
  6. "sync"
  7. "time"
  8. "code.gitea.io/gitea/models"
  9. "code.gitea.io/gitea/modules/graceful"
  10. "code.gitea.io/gitea/modules/log"
  11. "code.gitea.io/gitea/modules/setting"
  12. "code.gitea.io/gitea/modules/util"
  13. )
  14. // IndexerData data stored in the issue indexer
  15. type IndexerData struct {
  16. ID int64
  17. RepoID int64
  18. Title string
  19. Content string
  20. Comments []string
  21. IsDelete bool
  22. IDs []int64
  23. }
  24. // Match represents on search result
  25. type Match struct {
  26. ID int64 `json:"id"`
  27. RepoID int64 `json:"repo_id"`
  28. Score float64 `json:"score"`
  29. }
  30. // SearchResult represents search results
  31. type SearchResult struct {
  32. Total int64
  33. Hits []Match
  34. }
  35. // Indexer defines an inteface to indexer issues contents
  36. type Indexer interface {
  37. Init() (bool, error)
  38. Index(issue []*IndexerData) error
  39. Delete(ids ...int64) error
  40. Search(kw string, repoID int64, limit, start int) (*SearchResult, error)
  41. }
  42. type indexerHolder struct {
  43. indexer Indexer
  44. mutex sync.RWMutex
  45. cond *sync.Cond
  46. }
  47. func newIndexerHolder() *indexerHolder {
  48. h := &indexerHolder{}
  49. h.cond = sync.NewCond(h.mutex.RLocker())
  50. return h
  51. }
  52. func (h *indexerHolder) set(indexer Indexer) {
  53. h.mutex.Lock()
  54. defer h.mutex.Unlock()
  55. h.indexer = indexer
  56. h.cond.Broadcast()
  57. }
  58. func (h *indexerHolder) get() Indexer {
  59. h.mutex.RLock()
  60. defer h.mutex.RUnlock()
  61. if h.indexer == nil {
  62. h.cond.Wait()
  63. }
  64. return h.indexer
  65. }
  66. var (
  67. issueIndexerChannel = make(chan *IndexerData, setting.Indexer.UpdateQueueLength)
  68. // issueIndexerQueue queue of issue ids to be updated
  69. issueIndexerQueue Queue
  70. holder = newIndexerHolder()
  71. )
  72. // InitIssueIndexer initialize issue indexer, syncReindex is true then reindex until
  73. // all issue index done.
  74. func InitIssueIndexer(syncReindex bool) {
  75. waitChannel := make(chan time.Duration)
  76. go func() {
  77. start := time.Now()
  78. log.Info("Initializing Issue Indexer")
  79. var populate bool
  80. var dummyQueue bool
  81. switch setting.Indexer.IssueType {
  82. case "bleve":
  83. issueIndexer := NewBleveIndexer(setting.Indexer.IssuePath)
  84. exist, err := issueIndexer.Init()
  85. if err != nil {
  86. log.Fatal("Unable to initialize Bleve Issue Indexer: %v", err)
  87. }
  88. populate = !exist
  89. holder.set(issueIndexer)
  90. case "db":
  91. issueIndexer := &DBIndexer{}
  92. holder.set(issueIndexer)
  93. dummyQueue = true
  94. default:
  95. log.Fatal("Unknown issue indexer type: %s", setting.Indexer.IssueType)
  96. }
  97. if dummyQueue {
  98. issueIndexerQueue = &DummyQueue{}
  99. } else {
  100. var err error
  101. switch setting.Indexer.IssueQueueType {
  102. case setting.LevelQueueType:
  103. issueIndexerQueue, err = NewLevelQueue(
  104. holder.get(),
  105. setting.Indexer.IssueQueueDir,
  106. setting.Indexer.IssueQueueBatchNumber)
  107. if err != nil {
  108. log.Fatal(
  109. "Unable create level queue for issue queue dir: %s batch number: %d : %v",
  110. setting.Indexer.IssueQueueDir,
  111. setting.Indexer.IssueQueueBatchNumber,
  112. err)
  113. }
  114. case setting.ChannelQueueType:
  115. issueIndexerQueue = NewChannelQueue(holder.get(), setting.Indexer.IssueQueueBatchNumber)
  116. case setting.RedisQueueType:
  117. addrs, pass, idx, err := parseConnStr(setting.Indexer.IssueQueueConnStr)
  118. if err != nil {
  119. log.Fatal("Unable to parse connection string for RedisQueueType: %s : %v",
  120. setting.Indexer.IssueQueueConnStr,
  121. err)
  122. }
  123. issueIndexerQueue, err = NewRedisQueue(addrs, pass, idx, holder.get(), setting.Indexer.IssueQueueBatchNumber)
  124. if err != nil {
  125. log.Fatal("Unable to create RedisQueue: %s : %v",
  126. setting.Indexer.IssueQueueConnStr,
  127. err)
  128. }
  129. default:
  130. log.Fatal("Unsupported indexer queue type: %v",
  131. setting.Indexer.IssueQueueType)
  132. }
  133. go func() {
  134. err = issueIndexerQueue.Run()
  135. if err != nil {
  136. log.Error("issueIndexerQueue.Run: %v", err)
  137. }
  138. }()
  139. }
  140. go func() {
  141. for data := range issueIndexerChannel {
  142. _ = issueIndexerQueue.Push(data)
  143. }
  144. }()
  145. if populate {
  146. if syncReindex {
  147. populateIssueIndexer()
  148. } else {
  149. go populateIssueIndexer()
  150. }
  151. }
  152. waitChannel <- time.Since(start)
  153. }()
  154. if syncReindex {
  155. <-waitChannel
  156. } else if setting.Indexer.StartupTimeout > 0 {
  157. go func() {
  158. timeout := setting.Indexer.StartupTimeout
  159. if graceful.IsChild && setting.GracefulHammerTime > 0 {
  160. timeout += setting.GracefulHammerTime
  161. }
  162. select {
  163. case duration := <-waitChannel:
  164. log.Info("Issue Indexer Initialization took %v", duration)
  165. case <-time.After(timeout):
  166. log.Fatal("Issue Indexer Initialization timed-out after: %v", timeout)
  167. }
  168. }()
  169. }
  170. }
  171. // populateIssueIndexer populate the issue indexer with issue data
  172. func populateIssueIndexer() {
  173. for page := 1; ; page++ {
  174. repos, _, err := models.SearchRepositoryByName(&models.SearchRepoOptions{
  175. Page: page,
  176. PageSize: models.RepositoryListDefaultPageSize,
  177. OrderBy: models.SearchOrderByID,
  178. Private: true,
  179. Collaborate: util.OptionalBoolFalse,
  180. })
  181. if err != nil {
  182. log.Error("SearchRepositoryByName: %v", err)
  183. continue
  184. }
  185. if len(repos) == 0 {
  186. return
  187. }
  188. for _, repo := range repos {
  189. is, err := models.Issues(&models.IssuesOptions{
  190. RepoIDs: []int64{repo.ID},
  191. IsClosed: util.OptionalBoolNone,
  192. IsPull: util.OptionalBoolNone,
  193. })
  194. if err != nil {
  195. log.Error("Issues: %v", err)
  196. continue
  197. }
  198. if err = models.IssueList(is).LoadDiscussComments(); err != nil {
  199. log.Error("LoadComments: %v", err)
  200. continue
  201. }
  202. for _, issue := range is {
  203. UpdateIssueIndexer(issue)
  204. }
  205. }
  206. }
  207. }
  208. // UpdateIssueIndexer add/update an issue to the issue indexer
  209. func UpdateIssueIndexer(issue *models.Issue) {
  210. var comments []string
  211. for _, comment := range issue.Comments {
  212. if comment.Type == models.CommentTypeComment {
  213. comments = append(comments, comment.Content)
  214. }
  215. }
  216. issueIndexerChannel <- &IndexerData{
  217. ID: issue.ID,
  218. RepoID: issue.RepoID,
  219. Title: issue.Title,
  220. Content: issue.Content,
  221. Comments: comments,
  222. }
  223. }
  224. // DeleteRepoIssueIndexer deletes repo's all issues indexes
  225. func DeleteRepoIssueIndexer(repo *models.Repository) {
  226. var ids []int64
  227. ids, err := models.GetIssueIDsByRepoID(repo.ID)
  228. if err != nil {
  229. log.Error("getIssueIDsByRepoID failed: %v", err)
  230. return
  231. }
  232. if len(ids) == 0 {
  233. return
  234. }
  235. issueIndexerChannel <- &IndexerData{
  236. IDs: ids,
  237. IsDelete: true,
  238. }
  239. }
  240. // SearchIssuesByKeyword search issue ids by keywords and repo id
  241. func SearchIssuesByKeyword(repoID int64, keyword string) ([]int64, error) {
  242. var issueIDs []int64
  243. res, err := holder.get().Search(keyword, repoID, 1000, 0)
  244. if err != nil {
  245. return nil, err
  246. }
  247. for _, r := range res.Hits {
  248. issueIDs = append(issueIDs, r.ID)
  249. }
  250. return issueIDs, nil
  251. }
上海开阖软件有限公司 沪ICP备12045867号-1