Implement in-application mutex to reduce DB_LOCKED errors

This commit is contained in:
Mike Schwörer 2024-09-16 15:17:20 +02:00
parent 527a659a1b
commit ce641a3ffe
Signed by: Mikescher
GPG Key ID: D3C7172E0A70F8CF
23 changed files with 324 additions and 470 deletions

View File

@ -10,9 +10,7 @@
- ios purchase verification
- (!) use goext.ginWrapper
- (!!!) local lock to prevent database-locked errors (there are a lot when one client malfunctions and starts sending a lot of notifications)
- exerr.New | exerr.Wrap
#### UNSURE
@ -55,11 +53,6 @@
- Pagination for ListChannels / ListSubscriptions / ListClients / ListChannelSubscriptions / ListUserSubscriptions
- Use only single struct for DB|Model|JSON
* needs sq.Converter implementation
* needs to handle joined data
* rfctime.Time...
- use job superclass (copy from isi/bnet/?), reduce duplicate code
- admin panel (especially errors and requests)

View File

@ -57,7 +57,7 @@ func (h APIHandler) ListChannels(pctx ginext.PreContext) ginext.HTTPResponse {
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockRead, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
if permResp := ctx.CheckPermissionUserRead(u.UserID); permResp != nil {
return *permResp
@ -143,7 +143,7 @@ func (h APIHandler) GetChannel(pctx ginext.PreContext) ginext.HTTPResponse {
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockRead, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
if permResp := ctx.CheckPermissionUserRead(u.UserID); permResp != nil {
return *permResp
@ -197,7 +197,7 @@ func (h APIHandler) CreateChannel(pctx ginext.PreContext) ginext.HTTPResponse {
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockReadWrite, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
if permResp := ctx.CheckPermissionUserAdmin(u.UserID); permResp != nil {
return *permResp
@ -304,7 +304,7 @@ func (h APIHandler) UpdateChannel(pctx ginext.PreContext) ginext.HTTPResponse {
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockReadWrite, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
if permResp := ctx.CheckPermissionUserAdmin(u.UserID); permResp != nil {
return *permResp
@ -428,7 +428,7 @@ func (h APIHandler) ListChannelMessages(pctx ginext.PreContext) ginext.HTTPRespo
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockRead, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
trimmed := langext.Coalesce(q.Trimmed, true)

View File

@ -41,7 +41,7 @@ func (h APIHandler) ListClients(pctx ginext.PreContext) ginext.HTTPResponse {
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockRead, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
if permResp := ctx.CheckPermissionUserRead(u.UserID); permResp != nil {
return *permResp
@ -86,7 +86,7 @@ func (h APIHandler) GetClient(pctx ginext.PreContext) ginext.HTTPResponse {
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockRead, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
if permResp := ctx.CheckPermissionUserRead(u.UserID); permResp != nil {
return *permResp
@ -141,7 +141,7 @@ func (h APIHandler) AddClient(pctx ginext.PreContext) ginext.HTTPResponse {
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockReadWrite, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
if !b.ClientType.Valid() {
return ginresp.APIError(g, 400, apierr.INVALID_CLIENTTYPE, "Invalid ClientType", nil)
@ -196,7 +196,7 @@ func (h APIHandler) DeleteClient(pctx ginext.PreContext) ginext.HTTPResponse {
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockReadWrite, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
if permResp := ctx.CheckPermissionUserAdmin(u.UserID); permResp != nil {
return *permResp
@ -260,7 +260,7 @@ func (h APIHandler) UpdateClient(pctx ginext.PreContext) ginext.HTTPResponse {
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockReadWrite, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
if permResp := ctx.CheckPermissionUserAdmin(u.UserID); permResp != nil {
return *permResp

View File

@ -43,7 +43,7 @@ func (h APIHandler) ListUserKeys(pctx ginext.PreContext) ginext.HTTPResponse {
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockRead, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
if permResp := ctx.CheckPermissionUserAdmin(u.UserID); permResp != nil {
return *permResp
@ -88,7 +88,7 @@ func (h APIHandler) GetCurrentUserKey(pctx ginext.PreContext) ginext.HTTPRespons
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockRead, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
if permResp := ctx.CheckPermissionAny(); permResp != nil {
return *permResp
@ -142,7 +142,7 @@ func (h APIHandler) GetUserKey(pctx ginext.PreContext) ginext.HTTPResponse {
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockRead, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
if permResp := ctx.CheckPermissionUserAdmin(u.UserID); permResp != nil {
return *permResp
@ -199,7 +199,7 @@ func (h APIHandler) UpdateUserKey(pctx ginext.PreContext) ginext.HTTPResponse {
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockReadWrite, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
if permResp := ctx.CheckPermissionUserAdmin(u.UserID); permResp != nil {
return *permResp
@ -299,7 +299,7 @@ func (h APIHandler) CreateUserKey(pctx ginext.PreContext) ginext.HTTPResponse {
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockReadWrite, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
channels := langext.Coalesce(b.Channels, make([]models.ChannelID, 0))
@ -366,7 +366,7 @@ func (h APIHandler) DeleteUserKey(pctx ginext.PreContext) ginext.HTTPResponse {
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockReadWrite, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
if permResp := ctx.CheckPermissionUserAdmin(u.UserID); permResp != nil {
return *permResp

View File

@ -62,7 +62,7 @@ func (h APIHandler) ListMessages(pctx ginext.PreContext) ginext.HTTPResponse {
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockReadWrite, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
trimmed := langext.Coalesce(q.Trimmed, true)
@ -191,7 +191,7 @@ func (h APIHandler) GetMessage(pctx ginext.PreContext) ginext.HTTPResponse {
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockRead, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
if permResp := ctx.CheckPermissionAny(); permResp != nil {
return *permResp
@ -263,7 +263,7 @@ func (h APIHandler) DeleteMessage(pctx ginext.PreContext) ginext.HTTPResponse {
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockReadWrite, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
if permResp := ctx.CheckPermissionAny(); permResp != nil {
return *permResp

View File

@ -38,7 +38,7 @@ func (h APIHandler) GetUserPreview(pctx ginext.PreContext) ginext.HTTPResponse {
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockRead, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
if permResp := ctx.CheckPermissionAny(); permResp != nil {
return *permResp
@ -84,7 +84,7 @@ func (h APIHandler) GetChannelPreview(pctx ginext.PreContext) ginext.HTTPRespons
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockRead, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
if permResp := ctx.CheckPermissionAny(); permResp != nil {
return *permResp
@ -130,7 +130,7 @@ func (h APIHandler) GetUserKeyPreview(pctx ginext.PreContext) ginext.HTTPRespons
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockRead, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
if permResp := ctx.CheckPermissionAny(); permResp != nil {
return *permResp

View File

@ -71,7 +71,7 @@ func (h APIHandler) ListUserSubscriptions(pctx ginext.PreContext) ginext.HTTPRes
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockRead, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
if permResp := ctx.CheckPermissionUserRead(u.UserID); permResp != nil {
return *permResp
@ -166,7 +166,7 @@ func (h APIHandler) ListChannelSubscriptions(pctx ginext.PreContext) ginext.HTTP
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockRead, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
if permResp := ctx.CheckPermissionUserRead(u.UserID); permResp != nil {
return *permResp
@ -219,7 +219,7 @@ func (h APIHandler) GetSubscription(pctx ginext.PreContext) ginext.HTTPResponse
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockRead, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
if permResp := ctx.CheckPermissionUserRead(u.UserID); permResp != nil {
return *permResp
@ -270,7 +270,7 @@ func (h APIHandler) CancelSubscription(pctx ginext.PreContext) ginext.HTTPRespon
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockReadWrite, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
if permResp := ctx.CheckPermissionUserAdmin(u.UserID); permResp != nil {
return *permResp
@ -336,7 +336,7 @@ func (h APIHandler) CreateSubscription(pctx ginext.PreContext) ginext.HTTPRespon
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockReadWrite, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
if permResp := ctx.CheckPermissionUserAdmin(u.UserID); permResp != nil {
return *permResp
@ -440,7 +440,7 @@ func (h APIHandler) UpdateSubscription(pctx ginext.PreContext) ginext.HTTPRespon
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockReadWrite, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
if permResp := ctx.CheckPermissionUserAdmin(u.UserID); permResp != nil {
return *permResp

View File

@ -46,7 +46,7 @@ func (h APIHandler) CreateUser(pctx ginext.PreContext) ginext.HTTPResponse {
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockReadWrite, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
var clientType models.ClientType
if !b.NoClient {
@ -164,7 +164,7 @@ func (h APIHandler) GetUser(pctx ginext.PreContext) ginext.HTTPResponse {
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockRead, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
if permResp := ctx.CheckPermissionUserRead(u.UserID); permResp != nil {
return *permResp
@ -220,7 +220,7 @@ func (h APIHandler) UpdateUser(pctx ginext.PreContext) ginext.HTTPResponse {
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockReadWrite, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
if permResp := ctx.CheckPermissionUserAdmin(u.UserID); permResp != nil {
return *permResp

View File

@ -5,6 +5,7 @@ import (
"blackforestbytes.com/simplecloudnotifier/api/ginresp"
"blackforestbytes.com/simplecloudnotifier/db/simplectx"
"blackforestbytes.com/simplecloudnotifier/logic"
"blackforestbytes.com/simplecloudnotifier/models"
"bytes"
"errors"
"github.com/gin-gonic/gin"
@ -58,7 +59,7 @@ func (h CommonHandler) Ping(pctx ginext.PreContext) ginext.HTTPResponse {
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockRead, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
buf := new(bytes.Buffer)
_, _ = buf.ReadFrom(g.Request.Body)
@ -102,7 +103,7 @@ func (h CommonHandler) DatabaseTest(pctx ginext.PreContext) ginext.HTTPResponse
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockRead, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
libVersion, libVersionNumber, sourceID := sqlite3.Version()
@ -142,7 +143,7 @@ func (h CommonHandler) Health(pctx ginext.PreContext) ginext.HTTPResponse {
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockReadWrite, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
_, libVersionNumber, _ := sqlite3.Version()
@ -217,7 +218,7 @@ func (h CommonHandler) Sleep(pctx ginext.PreContext) ginext.HTTPResponse {
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockRead, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
t0 := time.Now().Format(time.RFC3339Nano)
@ -246,7 +247,7 @@ func (h CommonHandler) NoRoute(pctx ginext.PreContext) ginext.HTTPResponse {
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockRead, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return ginext.JSON(http.StatusNotFound, gin.H{
"": "================ ROUTE NOT FOUND ================",

View File

@ -78,7 +78,7 @@ func (h CompatHandler) SendMessage(pctx ginext.PreContext) ginext.HTTPResponse {
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockReadWrite, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
data := dataext.ObjectMerge(f, q)
@ -154,7 +154,7 @@ func (h CompatHandler) Register(pctx ginext.PreContext) ginext.HTTPResponse {
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockReadWrite, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
data := dataext.ObjectMerge(datb, datq)
@ -277,7 +277,7 @@ func (h CompatHandler) Info(pctx ginext.PreContext) ginext.HTTPResponse {
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockRead, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
data := dataext.ObjectMerge(datb, datq)
@ -386,7 +386,7 @@ func (h CompatHandler) Ack(pctx ginext.PreContext) ginext.HTTPResponse {
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockReadWrite, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
data := dataext.ObjectMerge(datb, datq)
@ -495,7 +495,7 @@ func (h CompatHandler) Requery(pctx ginext.PreContext) ginext.HTTPResponse {
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockReadWrite, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
data := dataext.ObjectMerge(datb, datq)
@ -616,7 +616,7 @@ func (h CompatHandler) Update(pctx ginext.PreContext) ginext.HTTPResponse {
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockReadWrite, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
data := dataext.ObjectMerge(datb, datq)
@ -747,7 +747,7 @@ func (h CompatHandler) Expand(pctx ginext.PreContext) ginext.HTTPResponse {
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockRead, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
data := dataext.ObjectMerge(datb, datq)
@ -867,7 +867,7 @@ func (h CompatHandler) Upgrade(pctx ginext.PreContext) ginext.HTTPResponse {
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockReadWrite, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
data := dataext.ObjectMerge(datb, datq)

View File

@ -80,7 +80,7 @@ func (h ExternalHandler) UptimeKuma(pctx ginext.PreContext) ginext.HTTPResponse
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockReadWrite, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
if b.Heartbeat == nil {
return ginresp.APIError(g, 400, apierr.BINDFAIL_BODY_PARAM, "missing field 'heartbeat' in request body", nil)

View File

@ -83,7 +83,7 @@ func (h MessageHandler) SendMessage(pctx ginext.PreContext) ginext.HTTPResponse
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockReadWrite, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
// query has highest prio, then form, then json
data := dataext.ObjectMerge(dataext.ObjectMerge(b, f), q)

View File

@ -3,6 +3,7 @@ package handler
import (
"blackforestbytes.com/simplecloudnotifier/api/ginresp"
"blackforestbytes.com/simplecloudnotifier/logic"
"blackforestbytes.com/simplecloudnotifier/models"
"blackforestbytes.com/simplecloudnotifier/website"
"errors"
"github.com/gin-gonic/gin"
@ -35,7 +36,7 @@ func (h WebsiteHandler) Index(pctx ginext.PreContext) ginext.HTTPResponse {
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockNone, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.serveAsset(g, "index.html", true)
})
}
@ -47,7 +48,7 @@ func (h WebsiteHandler) APIDocs(pctx ginext.PreContext) ginext.HTTPResponse {
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockNone, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.serveAsset(g, "api.html", true)
})
}
@ -59,7 +60,7 @@ func (h WebsiteHandler) APIDocsMore(pctx ginext.PreContext) ginext.HTTPResponse
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockNone, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.serveAsset(g, "api_more.html", true)
})
}
@ -71,7 +72,7 @@ func (h WebsiteHandler) MessageSent(pctx ginext.PreContext) ginext.HTTPResponse
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockNone, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.serveAsset(g, "message_sent.html", true)
})
}
@ -83,7 +84,7 @@ func (h WebsiteHandler) FaviconIco(pctx ginext.PreContext) ginext.HTTPResponse {
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockNone, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.serveAsset(g, "favicon.ico", false)
})
}
@ -95,7 +96,7 @@ func (h WebsiteHandler) FaviconPNG(pctx ginext.PreContext) ginext.HTTPResponse {
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockNone, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.serveAsset(g, "favicon.png", false)
})
}
@ -107,7 +108,7 @@ func (h WebsiteHandler) Javascript(pctx ginext.PreContext) ginext.HTTPResponse {
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockNone, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
type uri struct {
Filename string `uri:"fn"`
@ -134,7 +135,7 @@ func (h WebsiteHandler) CSS(pctx ginext.PreContext) ginext.HTTPResponse {
}
defer ctx.Cancel()
return h.app.DoRequest(ctx, g, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.app.DoRequest(ctx, g, models.TLockNone, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
return h.serveAsset(g, "css/"+u.Filename, false)
})
}

View File

@ -44,6 +44,7 @@ require (
github.com/rs/xid v1.6.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
github.com/viney-shih/go-lock v1.1.2 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.2 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect

View File

@ -90,6 +90,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
@ -100,6 +101,8 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/viney-shih/go-lock v1.1.2 h1:3TdGTiHZCPqBdTvFbQZQN/TRZzKF3KWw2rFEyKz3YqA=
github.com/viney-shih/go-lock v1.1.2/go.mod h1:Yijm78Ljteb3kRiJrbLAxVntkUukGu5uzSxq/xV7OO8=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
@ -128,6 +131,7 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
@ -162,6 +166,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/loremipsum.v1 v1.1.2 h1:12APklfJKuGszqZsrArW5QoQh03/W+qyCCjvnDuS6Tw=
gopkg.in/loremipsum.v1 v1.1.2/go.mod h1:TuRvzFuzuejXj+odBU6Tubp/EPUyGb9wmSvHenyP2Ts=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -10,6 +10,7 @@ import (
"context"
"errors"
"github.com/rs/zerolog/log"
golock "github.com/viney-shih/go-lock"
"gogs.mikescher.com/BlackForestBytes/goext/ginext"
"gogs.mikescher.com/BlackForestBytes/goext/rext"
"gogs.mikescher.com/BlackForestBytes/goext/syncext"
@ -38,14 +39,16 @@ type Application struct {
Port string
IsRunning *syncext.AtomicBool
RequestLogQueue chan models.RequestLog
MainDatabaseLock golock.RWMutex
}
func NewApp(db *DBPool) *Application {
return &Application{
Database: db,
stopChan: make(chan bool),
IsRunning: syncext.NewAtomicBool(false),
RequestLogQueue: make(chan models.RequestLog, 1024),
Database: db,
stopChan: make(chan bool),
IsRunning: syncext.NewAtomicBool(false),
RequestLogQueue: make(chan models.RequestLog, 1024),
MainDatabaseLock: golock.NewCASMutex(),
}
}

View File

@ -23,7 +23,7 @@ type RequestOptions struct {
IgnoreWrongContentType bool
}
func (app *Application) DoRequest(gectx *ginext.AppContext, g *gin.Context, fn func(ctx *AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
func (app *Application) DoRequest(gectx *ginext.AppContext, g *gin.Context, lockmode models.TransactionLockMode, fn func(ctx *AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
maxRetry := scn.Conf.RequestMaxRetry
retrySleep := scn.Conf.RequestRetrySleep
@ -40,6 +40,29 @@ func (app *Application) DoRequest(gectx *ginext.AppContext, g *gin.Context, fn f
wrap, stackTrace, panicObj := callPanicSafe(func(ctx *AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
dl, ok := ctx.Deadline()
if !ok {
dl = time.Now().Add(time.Second * 5)
}
if lockmode == models.TLockRead {
islock := app.MainDatabaseLock.RTryLockWithTimeout(dl.Sub(time.Now()))
if !islock {
return ginresp.APIError(g, 500, apierr.INTERNAL_EXCEPTION, "Failed to lock {MainDatabaseLock} [ro]", nil)
}
defer app.MainDatabaseLock.RUnlock()
} else if lockmode == models.TLockReadWrite {
islock := app.MainDatabaseLock.TryLockWithTimeout(dl.Sub(time.Now()))
if !islock {
return ginresp.APIError(g, 500, apierr.INTERNAL_EXCEPTION, "Failed to lock {MainDatabaseLock} [rw]", nil)
}
defer app.MainDatabaseLock.Unlock()
}
authheader := g.GetHeader("Authorization")
perm, err := app.getPermissions(actx, authheader)

View File

@ -5,7 +5,7 @@ package models
import "gogs.mikescher.com/BlackForestBytes/goext/langext"
import "gogs.mikescher.com/BlackForestBytes/goext/enums"
const ChecksumEnumGenerator = "8ffad0d7406eb7f17cbbfeff6fee6e6fa7156470203934ebd220c824e6e15e09" // GoExtVersion: 0.0.511
const ChecksumEnumGenerator = "902919af7c6d46bd6701b33e47308bad93d50cd10cdacaac739e5242819c4d7b" // GoExtVersion: 0.0.512
// ================================ ClientType ================================
//
@ -283,6 +283,86 @@ func TokenPermValuesDescriptionMeta() []enums.EnumDescriptionMetaValue {
}
}
// ================================ TransactionLockMode ================================
//
// File: lock.go
// StringEnum: true
// DescrEnum: false
// DataEnum: false
//
var __TransactionLockModeValues = []TransactionLockMode{
TLockNone,
TLockRead,
TLockReadWrite,
}
var __TransactionLockModeVarnames = map[TransactionLockMode]string{
TLockNone: "TLockNone",
TLockRead: "TLockRead",
TLockReadWrite: "TLockReadWrite",
}
func (e TransactionLockMode) Valid() bool {
return langext.InArray(e, __TransactionLockModeValues)
}
func (e TransactionLockMode) Values() []TransactionLockMode {
return __TransactionLockModeValues
}
func (e TransactionLockMode) ValuesAny() []any {
return langext.ArrCastToAny(__TransactionLockModeValues)
}
func (e TransactionLockMode) ValuesMeta() []enums.EnumMetaValue {
return TransactionLockModeValuesMeta()
}
func (e TransactionLockMode) String() string {
return string(e)
}
func (e TransactionLockMode) VarName() string {
if d, ok := __TransactionLockModeVarnames[e]; ok {
return d
}
return ""
}
func (e TransactionLockMode) TypeName() string {
return "TransactionLockMode"
}
func (e TransactionLockMode) PackageName() string {
return "models"
}
func (e TransactionLockMode) Meta() enums.EnumMetaValue {
return enums.EnumMetaValue{VarName: e.VarName(), Value: e, Description: nil}
}
func ParseTransactionLockMode(vv string) (TransactionLockMode, bool) {
for _, ev := range __TransactionLockModeValues {
if string(ev) == vv {
return ev, true
}
}
return "", false
}
func TransactionLockModeValues() []TransactionLockMode {
return __TransactionLockModeValues
}
func TransactionLockModeValuesMeta() []enums.EnumMetaValue {
return []enums.EnumMetaValue{
TLockNone.Meta(),
TLockRead.Meta(),
TLockReadWrite.Meta(),
}
}
// ================================ ================= ================================
func AllPackageEnums() []enums.Enum {
@ -290,5 +370,6 @@ func AllPackageEnums() []enums.Enum {
ClientTypeAndroid, // ClientType
DeliveryStatusRetry, // DeliveryStatus
PermAdmin, // TokenPerm
TLockNone, // TransactionLockMode
}
}

View File

@ -15,7 +15,7 @@ import "reflect"
import "regexp"
import "strings"
const ChecksumCharsetIDGenerator = "8ffad0d7406eb7f17cbbfeff6fee6e6fa7156470203934ebd220c824e6e15e09" // GoExtVersion: 0.0.511
const ChecksumCharsetIDGenerator = "902919af7c6d46bd6701b33e47308bad93d50cd10cdacaac739e5242819c4d7b" // GoExtVersion: 0.0.512
const idlen = 24

9
scnserver/models/lock.go Normal file
View File

@ -0,0 +1,9 @@
package models
type TransactionLockMode string //@enum:type
const (
TLockNone TransactionLockMode = "NONE"
TLockRead TransactionLockMode = "READ"
TLockReadWrite TransactionLockMode = "READ_WRITE"
)

View File

@ -19,63 +19,39 @@
"parameters": [
{
"type": "string",
"example": "test",
"name": "channel",
"in": "query"
},
{
"type": "string",
"example": "This is a message",
"name": "content",
"in": "query"
},
{
"type": "string",
"example": "P3TNH8mvv14fm",
"name": "key",
"in": "query"
},
{
"type": "string",
"example": "db8b0e6a-a08c-4646",
"name": "msg_id",
"in": "query"
},
{
"enum": [
0,
1,
2
],
"type": "integer",
"example": 1,
"name": "priority",
"in": "query"
},
{
"type": "string",
"example": "example-server",
"name": "sender_name",
"in": "query"
},
{
"type": "number",
"example": 1669824037,
"name": "timestamp",
"in": "query"
},
{
"type": "string",
"example": "Hello World",
"name": "title",
"in": "query"
},
{
"type": "string",
"example": "7725",
"type": "integer",
"name": "user_id",
"in": "query"
},
{
"type": "string",
"name": "user_key",
"in": "query"
},
{
"description": " ",
"name": "post_body",
@ -86,62 +62,38 @@
},
{
"type": "string",
"example": "test",
"name": "channel",
"in": "formData"
},
{
"type": "string",
"example": "This is a message",
"name": "content",
"in": "formData"
},
{
"type": "string",
"example": "P3TNH8mvv14fm",
"name": "key",
"in": "formData"
},
{
"type": "string",
"example": "db8b0e6a-a08c-4646",
"name": "msg_id",
"in": "formData"
},
{
"enum": [
0,
1,
2
],
"type": "integer",
"example": 1,
"name": "priority",
"in": "formData"
},
{
"type": "string",
"example": "example-server",
"name": "sender_name",
"in": "formData"
},
{
"type": "number",
"example": 1669824037,
"name": "timestamp",
"in": "formData"
},
{
"type": "string",
"example": "Hello World",
"name": "title",
"in": "formData"
},
{
"type": "string",
"example": "7725",
"type": "integer",
"name": "user_id",
"in": "formData"
},
{
"type": "string",
"name": "user_key",
"in": "formData"
}
],
"responses": {
@ -2765,63 +2717,39 @@
"parameters": [
{
"type": "string",
"example": "test",
"name": "channel",
"in": "query"
},
{
"type": "string",
"example": "This is a message",
"name": "content",
"in": "query"
},
{
"type": "string",
"example": "P3TNH8mvv14fm",
"name": "key",
"in": "query"
},
{
"type": "string",
"example": "db8b0e6a-a08c-4646",
"name": "msg_id",
"in": "query"
},
{
"enum": [
0,
1,
2
],
"type": "integer",
"example": 1,
"name": "priority",
"in": "query"
},
{
"type": "string",
"example": "example-server",
"name": "sender_name",
"in": "query"
},
{
"type": "number",
"example": 1669824037,
"name": "timestamp",
"in": "query"
},
{
"type": "string",
"example": "Hello World",
"name": "title",
"in": "query"
},
{
"type": "string",
"example": "7725",
"type": "integer",
"name": "user_id",
"in": "query"
},
{
"type": "string",
"name": "user_key",
"in": "query"
},
{
"description": " ",
"name": "post_body",
@ -2832,62 +2760,38 @@
},
{
"type": "string",
"example": "test",
"name": "channel",
"in": "formData"
},
{
"type": "string",
"example": "This is a message",
"name": "content",
"in": "formData"
},
{
"type": "string",
"example": "P3TNH8mvv14fm",
"name": "key",
"in": "formData"
},
{
"type": "string",
"example": "db8b0e6a-a08c-4646",
"name": "msg_id",
"in": "formData"
},
{
"enum": [
0,
1,
2
],
"type": "integer",
"example": 1,
"name": "priority",
"in": "formData"
},
{
"type": "string",
"example": "example-server",
"name": "sender_name",
"in": "formData"
},
{
"type": "number",
"example": 1669824037,
"name": "timestamp",
"in": "formData"
},
{
"type": "string",
"example": "Hello World",
"name": "title",
"in": "formData"
},
{
"type": "string",
"example": "7725",
"type": "integer",
"name": "user_id",
"in": "formData"
},
{
"type": "string",
"name": "user_key",
"in": "formData"
}
],
"responses": {
@ -2935,121 +2839,73 @@
"parameters": [
{
"type": "string",
"example": "test",
"name": "channel",
"in": "query"
},
{
"type": "string",
"example": "This is a message",
"name": "content",
"in": "query"
},
{
"type": "string",
"example": "P3TNH8mvv14fm",
"name": "key",
"in": "query"
},
{
"type": "string",
"example": "db8b0e6a-a08c-4646",
"name": "msg_id",
"in": "query"
},
{
"enum": [
0,
1,
2
],
"type": "integer",
"example": 1,
"name": "priority",
"in": "query"
},
{
"type": "string",
"example": "example-server",
"name": "sender_name",
"in": "query"
},
{
"type": "number",
"example": 1669824037,
"name": "timestamp",
"in": "query"
},
{
"type": "string",
"example": "Hello World",
"name": "title",
"in": "query"
},
{
"type": "string",
"example": "7725",
"type": "integer",
"name": "user_id",
"in": "query"
},
{
"type": "string",
"example": "test",
"name": "channel",
"in": "formData"
"name": "user_key",
"in": "query"
},
{
"type": "string",
"example": "This is a message",
"name": "content",
"in": "formData"
},
{
"type": "string",
"example": "P3TNH8mvv14fm",
"name": "key",
"in": "formData"
},
{
"type": "string",
"example": "db8b0e6a-a08c-4646",
"name": "msg_id",
"in": "formData"
},
{
"enum": [
0,
1,
2
],
"type": "integer",
"example": 1,
"name": "priority",
"in": "formData"
},
{
"type": "string",
"example": "example-server",
"name": "sender_name",
"in": "formData"
},
{
"type": "number",
"example": 1669824037,
"name": "timestamp",
"in": "formData"
},
{
"type": "string",
"example": "Hello World",
"name": "title",
"in": "formData"
},
{
"type": "string",
"example": "7725",
"type": "integer",
"name": "user_id",
"in": "formData"
},
{
"type": "string",
"name": "user_key",
"in": "formData"
}
],
"responses": {
@ -3547,46 +3403,26 @@
"handler.SendMessage.combined": {
"type": "object",
"properties": {
"channel": {
"type": "string",
"example": "test"
},
"content": {
"type": "string",
"example": "This is a message"
},
"key": {
"type": "string",
"example": "P3TNH8mvv14fm"
"type": "string"
},
"msg_id": {
"type": "string",
"example": "db8b0e6a-a08c-4646"
"type": "string"
},
"priority": {
"type": "integer",
"enum": [
0,
1,
2
],
"example": 1
},
"sender_name": {
"type": "string",
"example": "example-server"
"type": "integer"
},
"timestamp": {
"type": "number",
"example": 1669824037
"type": "number"
},
"title": {
"type": "string",
"example": "Hello World"
"type": "string"
},
"user_id": {
"type": "string",
"example": "7725"
"type": "integer"
},
"user_key": {
"type": "string"
}
}
},
@ -3615,7 +3451,7 @@
"type": "integer"
},
"scn_msg_id": {
"type": "string"
"type": "integer"
},
"success": {
"type": "boolean"
@ -4035,6 +3871,9 @@
"trimmed": {
"type": "boolean"
},
"used_key_id": {
"type": "string"
},
"usr_message_id": {
"type": "string"
}

View File

@ -329,36 +329,19 @@ definitions:
type: object
handler.SendMessage.combined:
properties:
channel:
example: test
type: string
content:
example: This is a message
type: string
key:
example: P3TNH8mvv14fm
type: string
msg_id:
example: db8b0e6a-a08c-4646
type: string
priority:
enum:
- 0
- 1
- 2
example: 1
type: integer
sender_name:
example: example-server
type: string
timestamp:
example: 1669824037
type: number
title:
example: Hello World
type: string
user_id:
example: "7725"
type: integer
user_key:
type: string
type: object
handler.SendMessage.response:
@ -378,7 +361,7 @@ definitions:
quota_max:
type: integer
scn_msg_id:
type: string
type: integer
success:
type: boolean
suppress_send:
@ -656,6 +639,8 @@ definitions:
type: string
trimmed:
type: boolean
used_key_id:
type: string
usr_message_id:
type: string
type: object
@ -800,90 +785,52 @@ paths:
description: All parameter can be set via query-parameter or the json body.
Only UserID, UserKey and Title are required
parameters:
- example: test
in: query
name: channel
type: string
- example: This is a message
in: query
- in: query
name: content
type: string
- example: P3TNH8mvv14fm
in: query
name: key
type: string
- example: db8b0e6a-a08c-4646
in: query
- in: query
name: msg_id
type: string
- enum:
- 0
- 1
- 2
example: 1
in: query
- in: query
name: priority
type: integer
- example: example-server
in: query
name: sender_name
type: string
- example: 1669824037
in: query
- in: query
name: timestamp
type: number
- example: Hello World
in: query
- in: query
name: title
type: string
- example: "7725"
in: query
- in: query
name: user_id
type: integer
- in: query
name: user_key
type: string
- description: ' '
in: body
name: post_body
schema:
$ref: '#/definitions/handler.SendMessage.combined'
- example: test
in: formData
name: channel
type: string
- example: This is a message
in: formData
- in: formData
name: content
type: string
- example: P3TNH8mvv14fm
in: formData
name: key
type: string
- example: db8b0e6a-a08c-4646
in: formData
- in: formData
name: msg_id
type: string
- enum:
- 0
- 1
- 2
example: 1
in: formData
- in: formData
name: priority
type: integer
- example: example-server
in: formData
name: sender_name
type: string
- example: 1669824037
in: formData
- in: formData
name: timestamp
type: number
- example: Hello World
in: formData
- in: formData
name: title
type: string
- example: "7725"
in: formData
- in: formData
name: user_id
type: integer
- in: formData
name: user_key
type: string
responses:
"200":
@ -2683,90 +2630,52 @@ paths:
description: All parameter can be set via query-parameter or the json body.
Only UserID, UserKey and Title are required
parameters:
- example: test
in: query
name: channel
type: string
- example: This is a message
in: query
- in: query
name: content
type: string
- example: P3TNH8mvv14fm
in: query
name: key
type: string
- example: db8b0e6a-a08c-4646
in: query
- in: query
name: msg_id
type: string
- enum:
- 0
- 1
- 2
example: 1
in: query
- in: query
name: priority
type: integer
- example: example-server
in: query
name: sender_name
type: string
- example: 1669824037
in: query
- in: query
name: timestamp
type: number
- example: Hello World
in: query
- in: query
name: title
type: string
- example: "7725"
in: query
- in: query
name: user_id
type: integer
- in: query
name: user_key
type: string
- description: ' '
in: body
name: post_body
schema:
$ref: '#/definitions/handler.SendMessage.combined'
- example: test
in: formData
name: channel
type: string
- example: This is a message
in: formData
- in: formData
name: content
type: string
- example: P3TNH8mvv14fm
in: formData
name: key
type: string
- example: db8b0e6a-a08c-4646
in: formData
- in: formData
name: msg_id
type: string
- enum:
- 0
- 1
- 2
example: 1
in: formData
- in: formData
name: priority
type: integer
- example: example-server
in: formData
name: sender_name
type: string
- example: 1669824037
in: formData
- in: formData
name: timestamp
type: number
- example: Hello World
in: formData
- in: formData
name: title
type: string
- example: "7725"
in: formData
- in: formData
name: user_id
type: integer
- in: formData
name: user_key
type: string
responses:
"200":
@ -2799,85 +2708,47 @@ paths:
description: All parameter can be set via query-parameter or form-data body.
Only UserID, UserKey and Title are required
parameters:
- example: test
in: query
name: channel
type: string
- example: This is a message
in: query
- in: query
name: content
type: string
- example: P3TNH8mvv14fm
in: query
name: key
type: string
- example: db8b0e6a-a08c-4646
in: query
- in: query
name: msg_id
type: string
- enum:
- 0
- 1
- 2
example: 1
in: query
- in: query
name: priority
type: integer
- example: example-server
in: query
name: sender_name
type: string
- example: 1669824037
in: query
- in: query
name: timestamp
type: number
- example: Hello World
in: query
- in: query
name: title
type: string
- example: "7725"
in: query
- in: query
name: user_id
type: integer
- in: query
name: user_key
type: string
- example: test
in: formData
name: channel
type: string
- example: This is a message
in: formData
- in: formData
name: content
type: string
- example: P3TNH8mvv14fm
in: formData
name: key
type: string
- example: db8b0e6a-a08c-4646
in: formData
- in: formData
name: msg_id
type: string
- enum:
- 0
- 1
- 2
example: 1
in: formData
- in: formData
name: priority
type: integer
- example: example-server
in: formData
name: sender_name
type: string
- example: 1669824037
in: formData
- in: formData
name: timestamp
type: number
- example: Hello World
in: formData
- in: formData
name: title
type: string
- example: "7725"
in: formData
- in: formData
name: user_id
type: integer
- in: formData
name: user_key
type: string
responses:
"200":

View File

@ -7,6 +7,7 @@ import (
tt "blackforestbytes.com/simplecloudnotifier/test/util"
"fmt"
"github.com/gin-gonic/gin"
"math/rand/v2"
"net/url"
"strings"
"testing"
@ -1341,8 +1342,14 @@ func TestSendParallel(t *testing.T) {
uid := r0["user_id"].(string)
sendtok := r0["send_key"].(string)
admintok := r0["admin_key"].(string)
count := 128
count := 512
chanNames := make([]string, 0)
for i := 0; i < count/50; i++ {
chanNames = append(chanNames, tt.ShortLipsum0(1))
}
sem := make(chan tt.Void, count) // semaphore pattern
for i := 0; i < count; i++ {
@ -1350,11 +1357,31 @@ func TestSendParallel(t *testing.T) {
defer func() {
sem <- tt.Void{}
}()
tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
"key": sendtok,
"user_id": uid,
"title": tt.ShortLipsum0(2),
})
if rand.Int()%2 == 0 {
tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
"key": sendtok,
"user_id": uid,
"title": tt.ShortLipsum0(2),
})
} else {
tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
"key": sendtok,
"user_id": uid,
"title": tt.ShortLipsum0(2),
"channel": chanNames[rand.IntN(len(chanNames))],
})
}
tt.RequestGet[tt.Void](t, baseUrl, fmt.Sprintf("/api/ping"))
tt.RequestAuthGet[gin.H](t, admintok, baseUrl, fmt.Sprintf("/api/v2/messages"))
tt.RequestAuthGet[gin.H](t, admintok, baseUrl, "/api/v2/users/"+uid)
tt.RequestAuthGet[gin.H](t, admintok, baseUrl, "/api/v2/users/"+uid+"/channels")
tt.RequestAuthGet[gin.H](t, admintok, baseUrl, "/api/v2/users/"+uid+"/clients")
}()
}
// wait for goroutines to finish