From ce641a3ffe10b1cbf88b4a593433f2d785191c1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Schw=C3=B6rer?= Date: Mon, 16 Sep 2024 15:17:20 +0200 Subject: [PATCH] Implement in-application mutex to reduce DB_LOCKED errors --- scnserver/TODO.md | 9 +- scnserver/api/handler/apiChannel.go | 10 +- scnserver/api/handler/apiClient.go | 10 +- scnserver/api/handler/apiKeyToken.go | 12 +- scnserver/api/handler/apiMessage.go | 6 +- scnserver/api/handler/apiPreview.go | 6 +- scnserver/api/handler/apiSubscription.go | 12 +- scnserver/api/handler/apiUser.go | 6 +- scnserver/api/handler/common.go | 11 +- scnserver/api/handler/compat.go | 16 +- scnserver/api/handler/external.go | 2 +- scnserver/api/handler/message.go | 2 +- scnserver/api/handler/website.go | 17 +- scnserver/go.mod | 1 + scnserver/go.sum | 5 + scnserver/logic/application.go | 11 +- scnserver/logic/request.go | 25 ++- scnserver/models/enums_gen.go | 83 +++++++- scnserver/models/ids_gen.go | 2 +- scnserver/models/lock.go | 9 + scnserver/swagger/swagger.json | 253 +++++------------------ scnserver/swagger/swagger.yaml | 247 ++++++---------------- scnserver/test/send_test.go | 39 +++- 23 files changed, 324 insertions(+), 470 deletions(-) create mode 100644 scnserver/models/lock.go diff --git a/scnserver/TODO.md b/scnserver/TODO.md index f947712..c21348b 100644 --- a/scnserver/TODO.md +++ b/scnserver/TODO.md @@ -10,10 +10,8 @@ - ios purchase verification - - (!) use goext.ginWrapper + - exerr.New | exerr.Wrap - - (!!!) local lock to prevent database-locked errors (there are a lot when one client malfunctions and starts sending a lot of notifications) - #### UNSURE - (?) default-priority for channels @@ -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) diff --git a/scnserver/api/handler/apiChannel.go b/scnserver/api/handler/apiChannel.go index b06a6bd..93172d3 100644 --- a/scnserver/api/handler/apiChannel.go +++ b/scnserver/api/handler/apiChannel.go @@ -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) diff --git a/scnserver/api/handler/apiClient.go b/scnserver/api/handler/apiClient.go index bbf8449..9e1a9d8 100644 --- a/scnserver/api/handler/apiClient.go +++ b/scnserver/api/handler/apiClient.go @@ -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 diff --git a/scnserver/api/handler/apiKeyToken.go b/scnserver/api/handler/apiKeyToken.go index c4016b0..f4dcbcf 100644 --- a/scnserver/api/handler/apiKeyToken.go +++ b/scnserver/api/handler/apiKeyToken.go @@ -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 diff --git a/scnserver/api/handler/apiMessage.go b/scnserver/api/handler/apiMessage.go index 6f0c70b..2631f31 100644 --- a/scnserver/api/handler/apiMessage.go +++ b/scnserver/api/handler/apiMessage.go @@ -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 diff --git a/scnserver/api/handler/apiPreview.go b/scnserver/api/handler/apiPreview.go index d9c258b..b6cb418 100644 --- a/scnserver/api/handler/apiPreview.go +++ b/scnserver/api/handler/apiPreview.go @@ -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 diff --git a/scnserver/api/handler/apiSubscription.go b/scnserver/api/handler/apiSubscription.go index 3c33570..38471af 100644 --- a/scnserver/api/handler/apiSubscription.go +++ b/scnserver/api/handler/apiSubscription.go @@ -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 diff --git a/scnserver/api/handler/apiUser.go b/scnserver/api/handler/apiUser.go index a943227..76a30fa 100644 --- a/scnserver/api/handler/apiUser.go +++ b/scnserver/api/handler/apiUser.go @@ -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 diff --git a/scnserver/api/handler/common.go b/scnserver/api/handler/common.go index 83e4e9f..68e6f34 100644 --- a/scnserver/api/handler/common.go +++ b/scnserver/api/handler/common.go @@ -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 ================", diff --git a/scnserver/api/handler/compat.go b/scnserver/api/handler/compat.go index 9c571c9..8d4de49 100644 --- a/scnserver/api/handler/compat.go +++ b/scnserver/api/handler/compat.go @@ -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) diff --git a/scnserver/api/handler/external.go b/scnserver/api/handler/external.go index 6f5725d..7ce7686 100644 --- a/scnserver/api/handler/external.go +++ b/scnserver/api/handler/external.go @@ -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) diff --git a/scnserver/api/handler/message.go b/scnserver/api/handler/message.go index a07246d..8fdc74d 100644 --- a/scnserver/api/handler/message.go +++ b/scnserver/api/handler/message.go @@ -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) diff --git a/scnserver/api/handler/website.go b/scnserver/api/handler/website.go index 8b9aefa..60bed31 100644 --- a/scnserver/api/handler/website.go +++ b/scnserver/api/handler/website.go @@ -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) }) } diff --git a/scnserver/go.mod b/scnserver/go.mod index 6aa56db..dc8daff 100644 --- a/scnserver/go.mod +++ b/scnserver/go.mod @@ -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 diff --git a/scnserver/go.sum b/scnserver/go.sum index 2d36218..95245ba 100644 --- a/scnserver/go.sum +++ b/scnserver/go.sum @@ -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= diff --git a/scnserver/logic/application.go b/scnserver/logic/application.go index 9869f6b..a240928 100644 --- a/scnserver/logic/application.go +++ b/scnserver/logic/application.go @@ -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(), } } diff --git a/scnserver/logic/request.go b/scnserver/logic/request.go index f5f6df9..f303b44 100644 --- a/scnserver/logic/request.go +++ b/scnserver/logic/request.go @@ -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) diff --git a/scnserver/models/enums_gen.go b/scnserver/models/enums_gen.go index 6db4230..5d4d091 100644 --- a/scnserver/models/enums_gen.go +++ b/scnserver/models/enums_gen.go @@ -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 } } diff --git a/scnserver/models/ids_gen.go b/scnserver/models/ids_gen.go index 1a96df4..381b12f 100644 --- a/scnserver/models/ids_gen.go +++ b/scnserver/models/ids_gen.go @@ -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 diff --git a/scnserver/models/lock.go b/scnserver/models/lock.go new file mode 100644 index 0000000..b77455b --- /dev/null +++ b/scnserver/models/lock.go @@ -0,0 +1,9 @@ +package models + +type TransactionLockMode string //@enum:type + +const ( + TLockNone TransactionLockMode = "NONE" + TLockRead TransactionLockMode = "READ" + TLockReadWrite TransactionLockMode = "READ_WRITE" +) diff --git a/scnserver/swagger/swagger.json b/scnserver/swagger/swagger.json index aa9984c..655ecd1 100644 --- a/scnserver/swagger/swagger.json +++ b/scnserver/swagger/swagger.json @@ -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" } diff --git a/scnserver/swagger/swagger.yaml b/scnserver/swagger/swagger.yaml index 0213be5..b11cfa0 100644 --- a/scnserver/swagger/swagger.yaml +++ b/scnserver/swagger/swagger.yaml @@ -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": diff --git a/scnserver/test/send_test.go b/scnserver/test/send_test.go index bd493f1..633d9e9 100644 --- a/scnserver/test/send_test.go +++ b/scnserver/test/send_test.go @@ -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