mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-11-01 06:50:00 +00:00
[bugfix] temporarily replace modernc.org/sqlite-v1.29.5
with gitlab.com/NyaaaWhatsUpDoc/sqlite-v1.29.5-concurrency-workaround
(#2811)
This commit is contained in:
parent
83e7847cdf
commit
85bc140b58
9 changed files with 31 additions and 95 deletions
2
go.mod
2
go.mod
|
@ -2,6 +2,8 @@ module github.com/superseriousbusiness/gotosocial
|
||||||
|
|
||||||
go 1.21
|
go 1.21
|
||||||
|
|
||||||
|
replace modernc.org/sqlite => gitlab.com/NyaaaWhatsUpDoc/sqlite v1.29.5-concurrency-workaround
|
||||||
|
|
||||||
toolchain go1.21.3
|
toolchain go1.21.3
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -721,6 +721,8 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
github.com/yuin/goldmark v1.7.0 h1:EfOIvIMZIzHdB/R/zVrikYLPPwJlfMcNczJFMs1m6sA=
|
github.com/yuin/goldmark v1.7.0 h1:EfOIvIMZIzHdB/R/zVrikYLPPwJlfMcNczJFMs1m6sA=
|
||||||
github.com/yuin/goldmark v1.7.0/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
github.com/yuin/goldmark v1.7.0/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
||||||
|
gitlab.com/NyaaaWhatsUpDoc/sqlite v1.29.5-concurrency-workaround h1:cyYnGCVJ0zLW2Q0pCepy++ERHegWcKpl5JD1MiTKUuw=
|
||||||
|
gitlab.com/NyaaaWhatsUpDoc/sqlite v1.29.5-concurrency-workaround/go.mod h1:S02dvcmm7TnTRvGhv8IGYyLnIt7AS2KPaB1F/71p75U=
|
||||||
go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R79oO62zWg=
|
go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R79oO62zWg=
|
||||||
go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng=
|
go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng=
|
||||||
go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8=
|
go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8=
|
||||||
|
@ -1121,8 +1123,6 @@ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||||
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
|
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
|
||||||
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
|
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
|
||||||
modernc.org/sqlite v1.29.5 h1:8l/SQKAjDtZFo9lkJLdk8g9JEOeYRG4/ghStDCCTiTE=
|
|
||||||
modernc.org/sqlite v1.29.5/go.mod h1:S02dvcmm7TnTRvGhv8IGYyLnIt7AS2KPaB1F/71p75U=
|
|
||||||
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
||||||
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
||||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||||
|
|
1
vendor/modernc.org/sqlite/AUTHORS
generated
vendored
1
vendor/modernc.org/sqlite/AUTHORS
generated
vendored
|
@ -22,6 +22,7 @@ Josh Klein <josh.klein@outlook.com>
|
||||||
Logan Snow <logansnow@protonmail.com>
|
Logan Snow <logansnow@protonmail.com>
|
||||||
Michael Hoffmann <mhoffm@posteo.de>
|
Michael Hoffmann <mhoffm@posteo.de>
|
||||||
Michael Rykov <mrykov@gmail.com>
|
Michael Rykov <mrykov@gmail.com>
|
||||||
|
Morgan Bazalgette <morgan@howl.moe>
|
||||||
Ross Light <ross@zombiezen.com>
|
Ross Light <ross@zombiezen.com>
|
||||||
Saed SayedAhmed <saadmtsa@gmail.com>
|
Saed SayedAhmed <saadmtsa@gmail.com>
|
||||||
Steffen Butzer <steffen(dot)butzer@outlook.com>
|
Steffen Butzer <steffen(dot)butzer@outlook.com>
|
||||||
|
|
1
vendor/modernc.org/sqlite/CONTRIBUTORS
generated
vendored
1
vendor/modernc.org/sqlite/CONTRIBUTORS
generated
vendored
|
@ -27,6 +27,7 @@ Mark Summerfield <mark@qtrac.eu>
|
||||||
Matthew Gabeler-Lee <fastcat@gmail.com>
|
Matthew Gabeler-Lee <fastcat@gmail.com>
|
||||||
Michael Hoffmann <mhoffm@posteo.de>
|
Michael Hoffmann <mhoffm@posteo.de>
|
||||||
Michael Rykov <mrykov@gmail.com>
|
Michael Rykov <mrykov@gmail.com>
|
||||||
|
Morgan Bazalgette <morgan@howl.moe>
|
||||||
Romain Le Disez <r.gitlab@ledisez.net>
|
Romain Le Disez <r.gitlab@ledisez.net>
|
||||||
Ross Light <ross@zombiezen.com>
|
Ross Light <ross@zombiezen.com>
|
||||||
Saed SayedAhmed <saadmtsa@gmail.com>
|
Saed SayedAhmed <saadmtsa@gmail.com>
|
||||||
|
|
2
vendor/modernc.org/sqlite/Makefile
generated
vendored
2
vendor/modernc.org/sqlite/Makefile
generated
vendored
|
@ -55,7 +55,7 @@ clean:
|
||||||
|
|
||||||
edit:
|
edit:
|
||||||
@touch log
|
@touch log
|
||||||
@if [ -f "Session.vim" ]; then novim -S & else novim -p Makefile all_test.go generator.go & fi
|
@if [ -f "Session.vim" ]; then novim -S & else novim -p Makefile go.mod builder.json all_test.go generator.go & fi
|
||||||
|
|
||||||
editor:
|
editor:
|
||||||
gofmt -l -s -w . 2>&1 | tee log-editor
|
gofmt -l -s -w . 2>&1 | tee log-editor
|
||||||
|
|
7
vendor/modernc.org/sqlite/README.md
generated
vendored
7
vendor/modernc.org/sqlite/README.md
generated
vendored
|
@ -16,7 +16,7 @@ allowing one of the maintainers to work on it also in office hours.
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
[godoc.org/modernc.org/sqlite](http://godoc.org/modernc.org/sqlite)
|
[pkg.go.dev/modernc.org/sqlite](https://pkg.go.dev/modernc.org/sqlite)
|
||||||
|
|
||||||
## Builders
|
## Builders
|
||||||
|
|
||||||
|
@ -74,3 +74,8 @@ RAM. Shown are the best of 3 runs.
|
||||||
TOTAL....................................................... 5.525s TOTAL....................................................... 4.637s
|
TOTAL....................................................... 5.525s TOTAL....................................................... 4.637s
|
||||||
|
|
||||||
This particular test executes 16.1% faster in the C version.
|
This particular test executes 16.1% faster in the C version.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
* Q: **How can I write to a database concurrently without getting the `database is locked` error (or `SQLITE_BUSY`)?**
|
||||||
|
* A: You can't. The C sqlite implementation does not allow concurrent writes, and this libary does not modify that behaviour. You can, however, use [DB.SetMaxOpenConns(1)](https://pkg.go.dev/database/sql#DB.SetMaxOpenConns) so that only 1 connection is ever used by the `DB`, allowing concurrent access to DB without making the writes concurrent. More information on issues [#65](https://gitlab.com/cznic/sqlite/-/issues/65) and [#106](https://gitlab.com/cznic/sqlite/-/issues/106).
|
||||||
|
|
4
vendor/modernc.org/sqlite/builder.json
generated
vendored
4
vendor/modernc.org/sqlite/builder.json
generated
vendored
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"autogen": "<none>",
|
"autogen": "<none>",
|
||||||
"autotag": "darwin/(amd64|arm64)|freebsd/(amd64|arm64)|linux/(386|amd64|arm|arm64|ppc64le|riscv64|s390x)|windows/(amd64|arm64)",
|
"autotag": "darwin/(amd64|arm64)|freebsd/(amd64|arm64)|linux/(386|amd64|arm|arm64|ppc64le|riscv64|s390x)|windows/(amd64|arm64)",
|
||||||
"autoupdate": "darwin/(amd64|arm64)|freebsd/(amd64|arm64)|linux/(386|amd64|arm|arm64|ppc64le|riscv64|s390x)|windows/(amd64|arm64)",
|
"autoupdate": "<none>",
|
||||||
"test": "darwin/(amd64|arm64)|freebsd/(amd64|arm64)|linux/(386|amd64|arm|arm64|ppc64le|riscv64|s390x)|windows/(amd64|arm64)"
|
"test": "darwin/(amd64|arm64)|freebsd/(amd64|arm64)|linux/(386|amd64|arm|arm64|loon64|ppc64le|riscv64|s390x)|windows/(amd64|arm64)"
|
||||||
}
|
}
|
||||||
|
|
100
vendor/modernc.org/sqlite/sqlite.go
generated
vendored
100
vendor/modernc.org/sqlite/sqlite.go
generated
vendored
|
@ -21,7 +21,6 @@
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
@ -491,17 +490,6 @@ func toNamedValues(vals []driver.Value) (r []driver.NamedValue) {
|
||||||
|
|
||||||
func (s *stmt) exec(ctx context.Context, args []driver.NamedValue) (r driver.Result, err error) {
|
func (s *stmt) exec(ctx context.Context, args []driver.NamedValue) (r driver.Result, err error) {
|
||||||
var pstmt uintptr
|
var pstmt uintptr
|
||||||
var done int32
|
|
||||||
if ctx != nil {
|
|
||||||
if ctxDone := ctx.Done(); ctxDone != nil {
|
|
||||||
select {
|
|
||||||
case <-ctxDone:
|
|
||||||
return nil, ctx.Err()
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
defer interruptOnDone(ctx, s.c, &done)()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if pstmt != 0 {
|
if pstmt != 0 {
|
||||||
|
@ -514,13 +502,13 @@ func (s *stmt) exec(ctx context.Context, args []driver.NamedValue) (r driver.Res
|
||||||
err = e
|
err = e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx != nil && atomic.LoadInt32(&done) != 0 {
|
|
||||||
r, err = nil, ctx.Err()
|
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
for psql := s.psql; *(*byte)(unsafe.Pointer(psql)) != 0 && atomic.LoadInt32(&done) == 0; {
|
for psql := s.psql; *(*byte)(unsafe.Pointer(psql)) != 0; {
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if pstmt, err = s.c.prepareV2(&psql); err != nil {
|
if pstmt, err = s.c.prepareV2(&psql); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -528,6 +516,7 @@ func (s *stmt) exec(ctx context.Context, args []driver.NamedValue) (r driver.Res
|
||||||
if pstmt == 0 {
|
if pstmt == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
err = func() (err error) {
|
err = func() (err error) {
|
||||||
n, err := s.c.bindParameterCount(pstmt)
|
n, err := s.c.bindParameterCount(pstmt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -604,17 +593,6 @@ func (s *stmt) Query(args []driver.Value) (driver.Rows, error) { //TODO StmtQuer
|
||||||
|
|
||||||
func (s *stmt) query(ctx context.Context, args []driver.NamedValue) (r driver.Rows, err error) {
|
func (s *stmt) query(ctx context.Context, args []driver.NamedValue) (r driver.Rows, err error) {
|
||||||
var pstmt uintptr
|
var pstmt uintptr
|
||||||
var done int32
|
|
||||||
if ctx != nil {
|
|
||||||
if ctxDone := ctx.Done(); ctxDone != nil {
|
|
||||||
select {
|
|
||||||
case <-ctxDone:
|
|
||||||
return nil, ctx.Err()
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
defer interruptOnDone(ctx, s.c, &done)()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var allocs []uintptr
|
var allocs []uintptr
|
||||||
|
|
||||||
|
@ -630,14 +608,16 @@ func (s *stmt) query(ctx context.Context, args []driver.NamedValue) (r driver.Ro
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx != nil && atomic.LoadInt32(&done) != 0 {
|
if r == nil && err == nil {
|
||||||
r, err = nil, ctx.Err()
|
|
||||||
} else if r == nil && err == nil {
|
|
||||||
r, err = newRows(s.c, pstmt, allocs, true)
|
r, err = newRows(s.c, pstmt, allocs, true)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
for psql := s.psql; *(*byte)(unsafe.Pointer(psql)) != 0 && atomic.LoadInt32(&done) == 0; {
|
for psql := s.psql; *(*byte)(unsafe.Pointer(psql)) != 0; {
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if pstmt, err = s.c.prepareV2(&psql); err != nil {
|
if pstmt, err = s.c.prepareV2(&psql); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -755,10 +735,6 @@ func (t *tx) exec(ctx context.Context, sql string) (err error) {
|
||||||
defer t.c.free(psql)
|
defer t.c.free(psql)
|
||||||
//TODO use t.conn.ExecContext() instead
|
//TODO use t.conn.ExecContext() instead
|
||||||
|
|
||||||
if ctx != nil && ctx.Done() != nil {
|
|
||||||
defer interruptOnDone(ctx, t.c, nil)()
|
|
||||||
}
|
|
||||||
|
|
||||||
if rc := sqlite3.Xsqlite3_exec(t.c.tls, t.c.db, psql, 0, 0, 0); rc != sqlite3.SQLITE_OK {
|
if rc := sqlite3.Xsqlite3_exec(t.c.tls, t.c.db, psql, 0, 0, 0); rc != sqlite3.SQLITE_OK {
|
||||||
return t.c.errstr(rc)
|
return t.c.errstr(rc)
|
||||||
}
|
}
|
||||||
|
@ -766,51 +742,10 @@ func (t *tx) exec(ctx context.Context, sql string) (err error) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// interruptOnDone sets up a goroutine to interrupt the provided db when the
|
|
||||||
// context is canceled, and returns a function the caller must defer so it
|
|
||||||
// doesn't interrupt after the caller finishes.
|
|
||||||
func interruptOnDone(
|
|
||||||
ctx context.Context,
|
|
||||||
c *conn,
|
|
||||||
done *int32,
|
|
||||||
) func() {
|
|
||||||
if done == nil {
|
|
||||||
var d int32
|
|
||||||
done = &d
|
|
||||||
}
|
|
||||||
|
|
||||||
donech := make(chan struct{})
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
// don't call interrupt if we were already done: it indicates that this
|
|
||||||
// call to exec is no longer running and we would be interrupting
|
|
||||||
// nothing, or even possibly an unrelated later call to exec.
|
|
||||||
if atomic.AddInt32(done, 1) == 1 {
|
|
||||||
c.interrupt(c.db)
|
|
||||||
}
|
|
||||||
case <-donech:
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// the caller is expected to defer this function
|
|
||||||
return func() {
|
|
||||||
// set the done flag so that a context cancellation right after the caller
|
|
||||||
// returns doesn't trigger a call to interrupt for some other statement.
|
|
||||||
atomic.AddInt32(done, 1)
|
|
||||||
close(donech)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type conn struct {
|
type conn struct {
|
||||||
db uintptr // *sqlite3.Xsqlite3
|
db uintptr // *sqlite3.Xsqlite3
|
||||||
tls *libc.TLS
|
tls *libc.TLS
|
||||||
|
|
||||||
// Context handling can cause conn.Close and conn.interrupt to be invoked
|
|
||||||
// concurrently.
|
|
||||||
sync.Mutex
|
|
||||||
|
|
||||||
writeTimeFormat string
|
writeTimeFormat string
|
||||||
beginMode string
|
beginMode string
|
||||||
}
|
}
|
||||||
|
@ -1333,13 +1268,7 @@ func (c *conn) prepareV2(zSQL *uintptr) (pstmt uintptr, err error) {
|
||||||
//
|
//
|
||||||
// void sqlite3_interrupt(sqlite3*);
|
// void sqlite3_interrupt(sqlite3*);
|
||||||
func (c *conn) interrupt(pdb uintptr) (err error) {
|
func (c *conn) interrupt(pdb uintptr) (err error) {
|
||||||
c.Lock() // Defend against race with .Close invoked by context handling.
|
|
||||||
|
|
||||||
defer c.Unlock()
|
|
||||||
|
|
||||||
if c.tls != nil {
|
|
||||||
sqlite3.Xsqlite3_interrupt(c.tls, pdb)
|
sqlite3.Xsqlite3_interrupt(c.tls, pdb)
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1460,15 +1389,11 @@ func (c *conn) Close() (err error) {
|
||||||
dmesg("conn %p: err %v", c, err)
|
dmesg("conn %p: err %v", c, err)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
c.Lock() // Defend against race with .interrupt invoked by context handling.
|
|
||||||
|
|
||||||
defer c.Unlock()
|
|
||||||
|
|
||||||
if c.db != 0 {
|
if c.db != 0 {
|
||||||
if err := c.closeV2(c.db); err != nil {
|
if err := c.closeV2(c.db); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
c.db = 0
|
c.db = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1476,6 +1401,7 @@ func (c *conn) Close() (err error) {
|
||||||
c.tls.Close()
|
c.tls.Close()
|
||||||
c.tls = nil
|
c.tls = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
3
vendor/modules.txt
vendored
3
vendor/modules.txt
vendored
|
@ -1286,7 +1286,7 @@ modernc.org/mathutil
|
||||||
# modernc.org/memory v1.7.2
|
# modernc.org/memory v1.7.2
|
||||||
## explicit; go 1.18
|
## explicit; go 1.18
|
||||||
modernc.org/memory
|
modernc.org/memory
|
||||||
# modernc.org/sqlite v1.29.5
|
# modernc.org/sqlite v1.29.5 => gitlab.com/NyaaaWhatsUpDoc/sqlite v1.29.5-concurrency-workaround
|
||||||
## explicit; go 1.20
|
## explicit; go 1.20
|
||||||
modernc.org/sqlite
|
modernc.org/sqlite
|
||||||
modernc.org/sqlite/lib
|
modernc.org/sqlite/lib
|
||||||
|
@ -1299,3 +1299,4 @@ modernc.org/token
|
||||||
# mvdan.cc/xurls/v2 v2.5.0
|
# mvdan.cc/xurls/v2 v2.5.0
|
||||||
## explicit; go 1.19
|
## explicit; go 1.19
|
||||||
mvdan.cc/xurls/v2
|
mvdan.cc/xurls/v2
|
||||||
|
# modernc.org/sqlite => gitlab.com/NyaaaWhatsUpDoc/sqlite v1.29.5-concurrency-workaround
|
||||||
|
|
Loading…
Reference in a new issue