Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9d22b4e299 | |||
| caa8f25fc1 | |||
| 2c5bc6d753 | |||
| 9b281e6bbe | |||
| 920e095301 | |||
| 29f7b67e6d | |||
| 164fe27bc8 | |||
| 54d0ba4fa5 | |||
| 191929853c | |||
| 070eabcbda | |||
| f2f9050372 | |||
| b90f1d26b4 | |||
| bddeb7f7ba | |||
| d64af39d28 | |||
| 772f889341 | |||
| 1e57398330 | |||
| dd6fc4b69c | |||
| c633024a9c | |||
| 8ffdbff72a | |||
| d609b8db8c | |||
| bf35e5510d | |||
| 1e1441f8bd | |||
| 3c55a3baca | |||
| b7f9743ade |
@@ -5,6 +5,7 @@ package db
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/util"
|
||||
)
|
||||
@@ -72,3 +73,27 @@ func (err ErrNotExist) Error() string {
|
||||
func (err ErrNotExist) Unwrap() error {
|
||||
return util.ErrNotExist
|
||||
}
|
||||
|
||||
// IsErrDeadlock checks whether err is a database deadlock.
|
||||
// MySQL returns error 1213 (ER_LOCK_DEADLOCK / SQLSTATE 40001).
|
||||
// PostgreSQL returns SQLSTATE 40P01 with "deadlock detected".
|
||||
// SQLite returns SQLITE_BUSY (error 5) with "database is locked".
|
||||
func IsErrDeadlock(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
msg := err.Error()
|
||||
// MySQL / MariaDB: "Error 1213 (40001): Deadlock found when trying to get lock"
|
||||
if strings.Contains(msg, "Error 1213") || strings.Contains(msg, "40001") {
|
||||
return true
|
||||
}
|
||||
// PostgreSQL: "deadlock detected"
|
||||
if strings.Contains(msg, "deadlock detected") {
|
||||
return true
|
||||
}
|
||||
// SQLite: "database is locked"
|
||||
if strings.Contains(msg, "database is locked") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIsErrDeadlock(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
err error
|
||||
want bool
|
||||
}{
|
||||
{name: "nil", err: nil, want: false},
|
||||
{name: "unrelated", err: errors.New("connection refused"), want: false},
|
||||
{name: "mysql 1213", err: errors.New("Error 1213 (40001): Deadlock found when trying to get lock; try restarting transaction"), want: true},
|
||||
{name: "mysql sqlstate", err: errors.New("SQLSTATE 40001: serialization failure"), want: true},
|
||||
{name: "postgres", err: errors.New("pq: deadlock detected"), want: true},
|
||||
{name: "sqlite", err: errors.New("database is locked"), want: true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.want, IsErrDeadlock(tt.err))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
actions_model "git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/actions"
|
||||
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
|
||||
@@ -344,7 +345,7 @@ func handleWorkflows(
|
||||
|
||||
run.NeedApproval = need
|
||||
|
||||
if err := PrepareRunAndInsert(ctx, dwf.Content, run, nil); err != nil {
|
||||
if err := prepareRunAndInsertWithRetry(ctx, dwf.Content, run); err != nil {
|
||||
log.Error("PrepareRunAndInsert: %v", err)
|
||||
continue
|
||||
}
|
||||
@@ -352,6 +353,54 @@ func handleWorkflows(
|
||||
return nil
|
||||
}
|
||||
|
||||
// prepareRunAndInsertWithRetry wraps PrepareRunAndInsert with retries on
|
||||
// database deadlocks. When multiple workflow runs are inserted for the same
|
||||
// event (e.g. several workflows triggered by a single pull_request), each
|
||||
// InsertRun transaction acquires an X-lock on the repository row (via
|
||||
// UpdateRepoRunsNumbers) and an index lock on action_run. Two concurrent
|
||||
// transactions can deadlock when each holds one lock and waits for the other.
|
||||
// InnoDB resolves this by killing the lighter transaction, but handleWorkflows
|
||||
// only logged the error and moved on — silently dropping the workflow run.
|
||||
// Retrying the insert is safe because the rolled-back transaction left no
|
||||
// partial state.
|
||||
func prepareRunAndInsertWithRetry(ctx context.Context, content []byte, run *actions_model.ActionRun) error {
|
||||
const maxRetries = 3
|
||||
backoff := 50 * time.Millisecond
|
||||
|
||||
// Save original values that InsertRun mutates inside its transaction.
|
||||
// On deadlock rollback these become stale and must be reset before retry.
|
||||
origTitle := run.Title
|
||||
|
||||
var err error
|
||||
for attempt := range maxRetries {
|
||||
if err = PrepareRunAndInsert(ctx, content, run, nil); err == nil {
|
||||
return nil
|
||||
}
|
||||
if !db.IsErrDeadlock(err) {
|
||||
return err
|
||||
}
|
||||
log.Warn("PrepareRunAndInsert deadlock (attempt %d/%d) for workflow %s in repo %d, retrying: %v",
|
||||
attempt+1, maxRetries, run.WorkflowID, run.RepoID, err)
|
||||
|
||||
// Reset fields that InsertRun sets inside the (now rolled-back) transaction
|
||||
// so the next attempt starts clean.
|
||||
run.ID = 0
|
||||
run.Index = 0
|
||||
run.Status = actions_model.StatusWaiting
|
||||
run.Title = origTitle
|
||||
run.ConcurrencyGroup = ""
|
||||
run.ConcurrencyCancel = false
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-time.After(backoff):
|
||||
}
|
||||
backoff *= 2
|
||||
}
|
||||
return fmt.Errorf("deadlock persisted after %d retries: %w", maxRetries, err)
|
||||
}
|
||||
|
||||
func newNotifyInputFromIssue(issue *issues_model.Issue, event webhook_module.HookEventType) *notifyInput {
|
||||
return newNotifyInput(issue.Repo, issue.Poster, event)
|
||||
}
|
||||
|
||||
@@ -150,16 +150,18 @@ func WebPathFromRequest(s string) WebPath {
|
||||
}
|
||||
|
||||
var multiHyphenRe = regexp.MustCompile(`-{2,}`)
|
||||
var nonAlphanumRe = regexp.MustCompile(`[^a-zA-Z0-9\-]`)
|
||||
var nonSlugRe = regexp.MustCompile(`[^a-zA-Z0-9+.\-]`)
|
||||
|
||||
// sanitizeWikiTitle converts a user-provided title into a clean, URL-friendly slug.
|
||||
// Spaces and special characters become hyphens, consecutive hyphens collapse to one.
|
||||
// Preserves: letters, digits, hyphens, plus signs (+), and dots (.)
|
||||
func sanitizeWikiTitle(title string) string {
|
||||
title = strings.TrimSpace(title)
|
||||
title = strings.ReplaceAll(title, " ", "-")
|
||||
title = nonAlphanumRe.ReplaceAllString(title, "-")
|
||||
title = nonSlugRe.ReplaceAllString(title, "-")
|
||||
title = multiHyphenRe.ReplaceAllString(title, "-")
|
||||
title = strings.Trim(title, "-")
|
||||
title = strings.NewReplacer("-+-", "-", "+-", "-", "-+", "-").Replace(title) // clean stray plus signs
|
||||
title = strings.Trim(title, "-+.")
|
||||
return title
|
||||
}
|
||||
|
||||
|
||||
+68
-58
@@ -1,93 +1,103 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
VERSION: 05.01.02
|
||||
VERSION: 05.22.00
|
||||
-->
|
||||
|
||||
<updates>
|
||||
<update>
|
||||
<name>MokoGitea</name>
|
||||
<description>MokoGitea update</description>
|
||||
<description>MokoGitea dev build.</description>
|
||||
<element>mokogitea</element>
|
||||
<type>application</type>
|
||||
<version>05.01.02</version>
|
||||
<client>server</client>
|
||||
<tags><tag>stable</tag></tags>
|
||||
<infourl title="MokoGitea">https://git.mokoconsulting.tech/MokoConsulting/MokoGitea/releases/tag/v1.26.1-moko.05.01.02</infourl>
|
||||
<client>site</client>
|
||||
<version>05.05.00-dev</version>
|
||||
<creationDate>2026-05-30</creationDate>
|
||||
<infourl title="MokoGitea">https://code.mokoconsulting.tech/MokoConsulting/MokoGitea/releases/tag/development</infourl>
|
||||
<downloads>
|
||||
<downloadurl type="full" format="docker">git.mokoconsulting.tech/mokoconsulting/mokogitea:v1.26.1-moko.05.01.02</downloadurl>
|
||||
<downloadurl type="full" format="zip">https://code.mokoconsulting.tech/MokoConsulting/MokoGitea/releases/download/development/mokogitea-05.05.00-dev.zip</downloadurl>
|
||||
</downloads>
|
||||
<sha256></sha256>
|
||||
<targetplatform name="mokogitea" version="((1\.25\.)|(1\.26\.))" />
|
||||
<sha256>4fee9eb03e4b819a63bce2ceb54fdce0d3eb8bf5b31460fcc42e5ecd75cc856e</sha256>
|
||||
<tags><tag>dev</tag></tags>
|
||||
<changelogurl>https://code.mokoconsulting.tech/MokoConsulting/MokoGitea/raw/branch/main/CHANGELOG.md</changelogurl>
|
||||
<maintainer>Moko Consulting</maintainer>
|
||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||
<targetplatform name="go" version=".*"/>
|
||||
</update>
|
||||
<update>
|
||||
<name>MokoGitea</name>
|
||||
<description>MokoGitea update</description>
|
||||
<description>MokoGitea alpha build.</description>
|
||||
<element>mokogitea</element>
|
||||
<type>application</type>
|
||||
<version>05.01.02</version>
|
||||
<client>server</client>
|
||||
<tags><tag>rc</tag></tags>
|
||||
<infourl title="MokoGitea RC">https://git.mokoconsulting.tech/MokoConsulting/MokoGitea/releases/tag/v1.26.1-moko.05.01.02</infourl>
|
||||
<client>site</client>
|
||||
<version>05.05.00-alpha</version>
|
||||
<creationDate>2026-05-30</creationDate>
|
||||
<infourl title="MokoGitea">https://code.mokoconsulting.tech/MokoConsulting/MokoGitea/releases/tag/alpha</infourl>
|
||||
<downloads>
|
||||
<downloadurl type="full" format="docker">git.mokoconsulting.tech/mokoconsulting/mokogitea:v1.26.1-moko.05.01.02</downloadurl>
|
||||
<downloadurl type="full" format="zip">https://code.mokoconsulting.tech/MokoConsulting/MokoGitea/releases/download/alpha/mokogitea-05.05.00-alpha.zip</downloadurl>
|
||||
</downloads>
|
||||
<sha256></sha256>
|
||||
<targetplatform name="mokogitea" version="((1\.25\.)|(1\.26\.))" />
|
||||
<maintainer>Moko Consulting</maintainer>
|
||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||
</update>
|
||||
<update>
|
||||
<name>MokoGitea</name>
|
||||
<description>MokoGitea update</description>
|
||||
<element>mokogitea</element>
|
||||
<type>application</type>
|
||||
<version>05.00.00</version>
|
||||
<client>server</client>
|
||||
<tags><tag>beta</tag></tags>
|
||||
<infourl title="MokoGitea Beta">https://git.mokoconsulting.tech/MokoConsulting/MokoGitea/releases/tag/v1.26.1-moko.05.00.00</infourl>
|
||||
<downloads>
|
||||
<downloadurl type="full" format="docker">git.mokoconsulting.tech/mokoconsulting/mokogitea:v1.26.1-moko.05.00.00</downloadurl>
|
||||
</downloads>
|
||||
<sha256></sha256>
|
||||
<targetplatform name="mokogitea" version="((1\.25\.)|(1\.26\.))" />
|
||||
<maintainer>Moko Consulting</maintainer>
|
||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||
</update>
|
||||
<update>
|
||||
<name>MokoGitea</name>
|
||||
<description>MokoGitea update</description>
|
||||
<element>mokogitea</element>
|
||||
<type>application</type>
|
||||
<version>05.00.00</version>
|
||||
<client>server</client>
|
||||
<sha256>4fee9eb03e4b819a63bce2ceb54fdce0d3eb8bf5b31460fcc42e5ecd75cc856e</sha256>
|
||||
<tags><tag>alpha</tag></tags>
|
||||
<infourl title="MokoGitea Alpha">https://git.mokoconsulting.tech/MokoConsulting/MokoGitea/releases/tag/v1.26.1-moko.05.00.00</infourl>
|
||||
<downloads>
|
||||
<downloadurl type="full" format="docker">git.mokoconsulting.tech/mokoconsulting/mokogitea:v1.26.1-moko.05.00.00</downloadurl>
|
||||
</downloads>
|
||||
<sha256></sha256>
|
||||
<targetplatform name="mokogitea" version="((1\.25\.)|(1\.26\.))" />
|
||||
<changelogurl>https://code.mokoconsulting.tech/MokoConsulting/MokoGitea/raw/branch/main/CHANGELOG.md</changelogurl>
|
||||
<maintainer>Moko Consulting</maintainer>
|
||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||
<targetplatform name="go" version=".*"/>
|
||||
</update>
|
||||
<update>
|
||||
<name>MokoGitea</name>
|
||||
<description>MokoGitea update</description>
|
||||
<description>MokoGitea beta build.</description>
|
||||
<element>mokogitea</element>
|
||||
<type>application</type>
|
||||
<version>06.00.00-dev</version>
|
||||
<client>server</client>
|
||||
<tags><tag>development</tag></tags>
|
||||
<infourl title="MokoGitea Dev">https://git.mokoconsulting.tech/MokoConsulting/MokoGitea/src/branch/dev</infourl>
|
||||
<client>site</client>
|
||||
<version>05.05.00-beta</version>
|
||||
<creationDate>2026-05-30</creationDate>
|
||||
<infourl title="MokoGitea">https://code.mokoconsulting.tech/MokoConsulting/MokoGitea/releases/tag/beta</infourl>
|
||||
<downloads>
|
||||
<downloadurl type="full" format="docker">git.mokoconsulting.tech/mokoconsulting/mokogitea:v1.26.1-moko.06.00.00-dev</downloadurl>
|
||||
<downloadurl type="full" format="zip">https://code.mokoconsulting.tech/MokoConsulting/MokoGitea/releases/download/beta/mokogitea-05.05.00-beta.zip</downloadurl>
|
||||
</downloads>
|
||||
<sha256></sha256>
|
||||
<targetplatform name="mokogitea" version="((1\.25\.)|(1\.26\.))" />
|
||||
<sha256>4fee9eb03e4b819a63bce2ceb54fdce0d3eb8bf5b31460fcc42e5ecd75cc856e</sha256>
|
||||
<tags><tag>beta</tag></tags>
|
||||
<changelogurl>https://code.mokoconsulting.tech/MokoConsulting/MokoGitea/raw/branch/main/CHANGELOG.md</changelogurl>
|
||||
<maintainer>Moko Consulting</maintainer>
|
||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||
<targetplatform name="go" version=".*"/>
|
||||
</update>
|
||||
<update>
|
||||
<name>MokoGitea</name>
|
||||
<description>MokoGitea rc build.</description>
|
||||
<element>mokogitea</element>
|
||||
<type>application</type>
|
||||
<client>site</client>
|
||||
<version>05.05.00-rc</version>
|
||||
<creationDate>2026-05-30</creationDate>
|
||||
<infourl title="MokoGitea">https://code.mokoconsulting.tech/MokoConsulting/MokoGitea/releases/tag/release-candidate</infourl>
|
||||
<downloads>
|
||||
<downloadurl type="full" format="zip">https://code.mokoconsulting.tech/MokoConsulting/MokoGitea/releases/download/release-candidate/mokogitea-05.05.00-rc.zip</downloadurl>
|
||||
</downloads>
|
||||
<sha256>4fee9eb03e4b819a63bce2ceb54fdce0d3eb8bf5b31460fcc42e5ecd75cc856e</sha256>
|
||||
<tags><tag>rc</tag></tags>
|
||||
<changelogurl>https://code.mokoconsulting.tech/MokoConsulting/MokoGitea/raw/branch/main/CHANGELOG.md</changelogurl>
|
||||
<maintainer>Moko Consulting</maintainer>
|
||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||
<targetplatform name="go" version=".*"/>
|
||||
</update>
|
||||
<update>
|
||||
<name>MokoGitea</name>
|
||||
<description>MokoGitea stable build.</description>
|
||||
<element>mokogitea</element>
|
||||
<type>application</type>
|
||||
<client>site</client>
|
||||
<version>05.22.00</version>
|
||||
<creationDate>2026-06-04</creationDate>
|
||||
<infourl title='MokoGitea'>https://git.mokoconsulting.tech/MokoConsulting/MokoGitea/releases/tag/stable</infourl>
|
||||
<downloads>
|
||||
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoGitea/releases/download/stable/mokogitea-05.22.00.zip</downloadurl>
|
||||
</downloads>
|
||||
<sha256>1da6f0829f87b98313f3cad6d25ce57ee2d5175a7f96a0eb29d63c24e50fc3e1</sha256>
|
||||
<tags><tag>stable</tag></tags>
|
||||
<changelogurl>https://git.mokoconsulting.tech/MokoConsulting/MokoGitea/raw/branch/main/CHANGELOG.md</changelogurl>
|
||||
<maintainer>Moko Consulting</maintainer>
|
||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||
<targetplatform name="go" version=".*" />
|
||||
</update>
|
||||
</updates>
|
||||
|
||||
Reference in New Issue
Block a user