Merge branch 'main' into domain_permission_subscriptions

This commit is contained in:
tobi 2024-12-19 09:41:51 +01:00
commit c3cfd0b000
32 changed files with 393 additions and 193 deletions

4
go.mod
View file

@ -31,7 +31,7 @@ require (
codeberg.org/gruf/go-debug v1.3.0 codeberg.org/gruf/go-debug v1.3.0
codeberg.org/gruf/go-errors/v2 v2.3.2 codeberg.org/gruf/go-errors/v2 v2.3.2
codeberg.org/gruf/go-fastcopy v1.1.3 codeberg.org/gruf/go-fastcopy v1.1.3
codeberg.org/gruf/go-ffmpreg v0.6.2 codeberg.org/gruf/go-ffmpreg v0.6.4
codeberg.org/gruf/go-iotools v0.0.0-20240710125620-934ae9c654cf codeberg.org/gruf/go-iotools v0.0.0-20240710125620-934ae9c654cf
codeberg.org/gruf/go-kv v1.6.5 codeberg.org/gruf/go-kv v1.6.5
codeberg.org/gruf/go-list v0.0.0-20240425093752-494db03d641f codeberg.org/gruf/go-list v0.0.0-20240425093752-494db03d641f
@ -62,7 +62,7 @@ require (
github.com/miekg/dns v1.1.62 github.com/miekg/dns v1.1.62
github.com/minio/minio-go/v7 v7.0.81 github.com/minio/minio-go/v7 v7.0.81
github.com/mitchellh/mapstructure v1.5.0 github.com/mitchellh/mapstructure v1.5.0
github.com/ncruces/go-sqlite3 v0.21.0 github.com/ncruces/go-sqlite3 v0.21.2
github.com/oklog/ulid v1.3.1 github.com/oklog/ulid v1.3.1
github.com/prometheus/client_golang v1.20.5 github.com/prometheus/client_golang v1.20.5
github.com/spf13/cobra v1.8.1 github.com/spf13/cobra v1.8.1

8
go.sum generated
View file

@ -46,8 +46,8 @@ codeberg.org/gruf/go-fastcopy v1.1.3 h1:Jo9VTQjI6KYimlw25PPc7YLA3Xm+XMQhaHwKnM7x
codeberg.org/gruf/go-fastcopy v1.1.3/go.mod h1:GDDYR0Cnb3U/AIfGM3983V/L+GN+vuwVMvrmVABo21s= codeberg.org/gruf/go-fastcopy v1.1.3/go.mod h1:GDDYR0Cnb3U/AIfGM3983V/L+GN+vuwVMvrmVABo21s=
codeberg.org/gruf/go-fastpath/v2 v2.0.0 h1:iAS9GZahFhyWEH0KLhFEJR+txx1ZhMXxYzu2q5Qo9c0= codeberg.org/gruf/go-fastpath/v2 v2.0.0 h1:iAS9GZahFhyWEH0KLhFEJR+txx1ZhMXxYzu2q5Qo9c0=
codeberg.org/gruf/go-fastpath/v2 v2.0.0/go.mod h1:3pPqu5nZjpbRrOqvLyAK7puS1OfEtQvjd6342Cwz56Q= codeberg.org/gruf/go-fastpath/v2 v2.0.0/go.mod h1:3pPqu5nZjpbRrOqvLyAK7puS1OfEtQvjd6342Cwz56Q=
codeberg.org/gruf/go-ffmpreg v0.6.2 h1:VNYfV7bQgAcY9/xk5mBNk9IBethJhHS0guUoptmgnPQ= codeberg.org/gruf/go-ffmpreg v0.6.4 h1:TaTx3SW1+PhJXgr1LUZF+/LHWg/8Oe8cDLJyMOsIPb8=
codeberg.org/gruf/go-ffmpreg v0.6.2/go.mod h1:HQmEaBF83rHOt2Jo1yJv9D0JApoSLFtVR9Uzu7aVglk= codeberg.org/gruf/go-ffmpreg v0.6.4/go.mod h1:HQmEaBF83rHOt2Jo1yJv9D0JApoSLFtVR9Uzu7aVglk=
codeberg.org/gruf/go-iotools v0.0.0-20240710125620-934ae9c654cf h1:84s/ii8N6lYlskZjHH+DG6jyia8w2mXMZlRwFn8Gs3A= codeberg.org/gruf/go-iotools v0.0.0-20240710125620-934ae9c654cf h1:84s/ii8N6lYlskZjHH+DG6jyia8w2mXMZlRwFn8Gs3A=
codeberg.org/gruf/go-iotools v0.0.0-20240710125620-934ae9c654cf/go.mod h1:zZAICsp5rY7+hxnws2V0ePrWxE0Z2Z/KXcN3p/RQCfk= codeberg.org/gruf/go-iotools v0.0.0-20240710125620-934ae9c654cf/go.mod h1:zZAICsp5rY7+hxnws2V0ePrWxE0Z2Z/KXcN3p/RQCfk=
codeberg.org/gruf/go-kv v1.6.5 h1:ttPf0NA8F79pDqBttSudPTVCZmGncumeNIxmeM9ztz0= codeberg.org/gruf/go-kv v1.6.5 h1:ttPf0NA8F79pDqBttSudPTVCZmGncumeNIxmeM9ztz0=
@ -434,8 +434,8 @@ github.com/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs=
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/ncruces/go-sqlite3 v0.21.0 h1:EwKFoy1hHEopN4sFZarmi+McXdbCcbTuLixhEayXVbQ= github.com/ncruces/go-sqlite3 v0.21.2 h1:X7Ao4BwtS9h308lFtZA/stkvrzEHvAdp8g4Gko7Ehjs=
github.com/ncruces/go-sqlite3 v0.21.0/go.mod h1:zxMOaSG5kFYVFK4xQa0pdwIszqxqJ0W0BxBgwdrNjuA= github.com/ncruces/go-sqlite3 v0.21.2/go.mod h1:zxMOaSG5kFYVFK4xQa0pdwIszqxqJ0W0BxBgwdrNjuA=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M= github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=

View file

@ -54,7 +54,7 @@ func (m *Module) aboutGETHandler(c *gin.Context) {
Template: "about.tmpl", Template: "about.tmpl",
Instance: instance, Instance: instance,
OGMeta: apiutil.OGBase(instance), OGMeta: apiutil.OGBase(instance),
Stylesheets: []string{cssAbout, instanceCustomCSSPath}, Stylesheets: []string{cssAbout},
Extra: map[string]any{ Extra: map[string]any{
"showStrap": true, "showStrap": true,
"blocklistExposed": config.GetInstanceExposeSuspendedWeb(), "blocklistExposed": config.GetInstanceExposeSuspendedWeb(),

View file

@ -127,9 +127,8 @@ func (m *Module) confirmEmailPOSTHandler(c *gin.Context) {
// Serve page informing user that their // Serve page informing user that their
// email address is now confirmed. // email address is now confirmed.
page := apiutil.WebPage{ page := apiutil.WebPage{
Template: "confirmed_email.tmpl", Template: "confirmed_email.tmpl",
Instance: instance, Instance: instance,
Stylesheets: []string{instanceCustomCSSPath},
Extra: map[string]any{ Extra: map[string]any{
"email": user.Email, "email": user.Email,
"username": user.Account.Username, "username": user.Account.Username,

View file

@ -67,7 +67,7 @@ func (m *Module) domainBlockListGETHandler(c *gin.Context) {
Template: "domain-blocklist.tmpl", Template: "domain-blocklist.tmpl",
Instance: instance, Instance: instance,
OGMeta: apiutil.OGBase(instance), OGMeta: apiutil.OGBase(instance),
Stylesheets: []string{cssFA, instanceCustomCSSPath}, Stylesheets: []string{cssFA},
Javascript: []string{jsFrontend}, Javascript: []string{jsFrontend},
Extra: map[string]any{"blocklist": domainBlocks}, Extra: map[string]any{"blocklist": domainBlocks},
} }

View file

@ -59,7 +59,7 @@ func (m *Module) indexHandler(c *gin.Context) {
Template: "index.tmpl", Template: "index.tmpl",
Instance: instance, Instance: instance,
OGMeta: apiutil.OGBase(instance), OGMeta: apiutil.OGBase(instance),
Stylesheets: []string{cssAbout, cssIndex, instanceCustomCSSPath}, Stylesheets: []string{cssAbout, cssIndex},
Extra: map[string]any{"showStrap": true}, Extra: map[string]any{"showStrap": true},
} }

View file

@ -142,7 +142,6 @@ func (m *Module) profileGETHandler(c *gin.Context) {
cssStatus, cssStatus,
cssThread, cssThread,
cssProfile, cssProfile,
instanceCustomCSSPath,
}..., }...,
) )

View file

@ -53,7 +53,6 @@ func (m *Module) SettingsPanelHandler(c *gin.Context) {
cssProfile, // Used for rendering stub/fake profiles. cssProfile, // Used for rendering stub/fake profiles.
cssStatus, // Used for rendering stub/fake statuses. cssStatus, // Used for rendering stub/fake statuses.
cssSettings, cssSettings,
instanceCustomCSSPath,
}, },
Javascript: []string{jsSettings}, Javascript: []string{jsSettings},
} }

View file

@ -126,10 +126,9 @@ func (m *Module) signupPOSTHandler(c *gin.Context) {
// Serve a page informing the // Serve a page informing the
// user that they've signed up. // user that they've signed up.
page := apiutil.WebPage{ page := apiutil.WebPage{
Template: "signed-up.tmpl", Template: "signed-up.tmpl",
Instance: instance, Instance: instance,
Stylesheets: []string{instanceCustomCSSPath}, OGMeta: apiutil.OGBase(instance),
OGMeta: apiutil.OGBase(instance),
Extra: map[string]any{ Extra: map[string]any{
"email": user.UnconfirmedEmail, "email": user.UnconfirmedEmail,
"username": user.Account.Username, "username": user.Account.Username,

View file

@ -59,7 +59,7 @@ func (m *Module) tagGETHandler(c *gin.Context) {
Template: "tag.tmpl", Template: "tag.tmpl",
Instance: instance, Instance: instance,
OGMeta: apiutil.OGBase(instance), OGMeta: apiutil.OGBase(instance),
Stylesheets: []string{cssFA, cssThread, cssTag, instanceCustomCSSPath}, Stylesheets: []string{cssFA, cssThread, cssTag},
Extra: map[string]any{"tagName": tagName}, Extra: map[string]any{"tagName": tagName},
} }

View file

@ -124,7 +124,6 @@ func (m *Module) threadGETHandler(c *gin.Context) {
cssFA, cssFA,
cssStatus, cssStatus,
cssThread, cssThread,
instanceCustomCSSPath,
}..., }...,
) )

Binary file not shown.

View file

@ -74,7 +74,7 @@ This project aims for [high test coverage](https://github.com/ncruces/go-sqlite3
It also benefits greatly from [SQLite's](https://sqlite.org/testing.html) and It also benefits greatly from [SQLite's](https://sqlite.org/testing.html) and
[wazero's](https://tetrate.io/blog/introducing-wazero-from-tetrate/#:~:text=Rock%2Dsolid%20test%20approach) thorough testing. [wazero's](https://tetrate.io/blog/introducing-wazero-from-tetrate/#:~:text=Rock%2Dsolid%20test%20approach) thorough testing.
Every commit is [tested](https://github.com/ncruces/go-sqlite3/wiki/Test-matrix) on Every commit is [tested](https://github.com/ncruces/go-sqlite3/wiki/Support-matrix) on
Linux (amd64/arm64/386/riscv64/ppc64le/s390x), macOS (amd64/arm64), Linux (amd64/arm64/386/riscv64/ppc64le/s390x), macOS (amd64/arm64),
Windows (amd64), FreeBSD (amd64), OpenBSD (amd64), NetBSD (amd64), Windows (amd64), FreeBSD (amd64), OpenBSD (amd64), NetBSD (amd64),
DragonFly BSD (amd64), illumos (amd64), and Solaris (amd64). DragonFly BSD (amd64), illumos (amd64), and Solaris (amd64).

View file

@ -4,6 +4,7 @@
"context" "context"
"fmt" "fmt"
"math" "math"
"math/rand"
"net/url" "net/url"
"strings" "strings"
"time" "time"
@ -24,7 +25,6 @@ type Conn struct {
interrupt context.Context interrupt context.Context
pending *Stmt pending *Stmt
stmts []*Stmt stmts []*Stmt
timer *time.Timer
busy func(context.Context, int) bool busy func(context.Context, int) bool
log func(xErrorCode, string) log func(xErrorCode, string)
collation func(*Conn, string) collation func(*Conn, string)
@ -36,7 +36,9 @@ type Conn struct {
rollback func() rollback func()
arena arena arena arena
handle uint32 busy1st time.Time
busylst time.Time
handle uint32
} }
// Open calls [OpenFlags] with [OPEN_READWRITE], [OPEN_CREATE] and [OPEN_URI]. // Open calls [OpenFlags] with [OPEN_READWRITE], [OPEN_CREATE] and [OPEN_URI].
@ -389,38 +391,20 @@ func (c *Conn) BusyTimeout(timeout time.Duration) error {
} }
func timeoutCallback(ctx context.Context, mod api.Module, count, tmout int32) (retry uint32) { func timeoutCallback(ctx context.Context, mod api.Module, count, tmout int32) (retry uint32) {
// https://fractaledmind.github.io/2024/04/15/sqlite-on-rails-the-how-and-why-of-optimal-performance/
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.interrupt.Err() == nil { if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.interrupt.Err() == nil {
const delays = "\x01\x02\x05\x0a\x0f\x14\x19\x19\x19\x32\x32\x64" switch {
const totals = "\x00\x01\x03\x08\x12\x21\x35\x4e\x67\x80\xb2\xe4" case count == 0:
const ndelay = int32(len(delays) - 1) c.busy1st = time.Now()
case time.Since(c.busy1st) >= time.Duration(tmout)*time.Millisecond:
var delay, prior int32 return 0
if count <= ndelay {
delay = int32(delays[count])
prior = int32(totals[count])
} else {
delay = int32(delays[ndelay])
prior = int32(totals[ndelay]) + delay*(count-ndelay)
} }
if time.Since(c.busylst) < time.Millisecond {
if delay = min(delay, tmout-prior); delay > 0 { const sleepIncrement = 2*1024*1024 - 1 // power of two, ~2ms
delay := time.Duration(delay) * time.Millisecond time.Sleep(time.Duration(rand.Int63() & sleepIncrement))
if c.interrupt.Done() == nil {
time.Sleep(delay)
return 1
}
if c.timer == nil {
c.timer = time.NewTimer(delay)
} else {
c.timer.Reset(delay)
}
select {
case <-c.interrupt.Done():
c.timer.Stop()
case <-c.timer.C:
return 1
}
} }
c.busylst = time.Now()
return 1
} }
return 0 return 0
} }

View file

@ -379,7 +379,7 @@ func (c *conn) PrepareContext(ctx context.Context, query string) (driver.Stmt, e
if err != nil { if err != nil {
return nil, err return nil, err
} }
if tail != "" { if notWhitespace(tail) {
s.Close() s.Close()
return nil, util.TailErr return nil, util.TailErr
} }

View file

@ -0,0 +1,61 @@
package driver
func notWhitespace(sql string) bool {
const (
code = iota
slash
minus
ccomment
sqlcomment
endcomment
)
state := code
for _, b := range ([]byte)(sql) {
if b == 0 {
break
}
switch state {
case code:
switch b {
case '/':
state = slash
case '-':
state = minus
case ' ', ';', '\t', '\n', '\v', '\f', '\r':
continue
default:
return true
}
case slash:
if b != '*' {
return true
}
state = ccomment
case minus:
if b != '-' {
return true
}
state = sqlcomment
case ccomment:
if b == '*' {
state = endcomment
}
case sqlcomment:
if b == '\n' {
state = code
}
case endcomment:
switch b {
case '/':
state = code
case '*':
state = endcomment
default:
state = ccomment
}
}
}
return state == slash || state == minus
}

Binary file not shown.

View file

@ -1,3 +1,4 @@
github.com/ncruces/go-sqlite3 v0.21.0/go.mod h1:zxMOaSG5kFYVFK4xQa0pdwIszqxqJ0W0BxBgwdrNjuA=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=

View file

@ -0,0 +1,29 @@
package dotlk
import (
"errors"
"io/fs"
"os"
)
// LockShm creates a directory on disk to prevent SQLite
// from using this path for a shared memory file.
func LockShm(name string) error {
err := os.Mkdir(name, 0777)
if errors.Is(err, fs.ErrExist) {
s, err := os.Lstat(name)
if err == nil && s.IsDir() {
return nil
}
}
return err
}
// Unlock removes the lock or shared memory file.
func Unlock(name string) error {
err := os.Remove(name)
if errors.Is(err, fs.ErrNotExist) {
return nil
}
return err
}

View file

@ -0,0 +1,13 @@
//go:build !unix
package dotlk
import "os"
// TryLock returns nil if it acquired the lock,
// fs.ErrExist if another process has the lock.
func TryLock(name string) error {
f, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
f.Close()
return err
}

View file

@ -0,0 +1,50 @@
//go:build unix
package dotlk
import (
"errors"
"io/fs"
"os"
"strconv"
"golang.org/x/sys/unix"
)
// TryLock returns nil if it acquired the lock,
// fs.ErrExist if another process has the lock.
func TryLock(name string) error {
for retry := true; retry; retry = false {
f, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
if err == nil {
f.WriteString(strconv.Itoa(os.Getpid()))
f.Close()
return nil
}
if !errors.Is(err, fs.ErrExist) {
return err
}
if !removeStale(name) {
break
}
}
return fs.ErrExist
}
func removeStale(name string) bool {
buf, err := os.ReadFile(name)
if err != nil {
return errors.Is(err, fs.ErrNotExist)
}
pid, err := strconv.Atoi(string(buf))
if err != nil {
return false
}
if unix.Kill(pid, 0) == nil {
return false
}
err = os.Remove(name)
return err == nil || errors.Is(err, fs.ErrNotExist)
}

View file

@ -48,11 +48,6 @@ On Unix, this package may use `mmap` to implement
[shared-memory for the WAL-index](https://sqlite.org/wal.html#implementation_of_shared_memory_for_the_wal_index), [shared-memory for the WAL-index](https://sqlite.org/wal.html#implementation_of_shared_memory_for_the_wal_index),
like SQLite. like SQLite.
With [BSD locks](https://man.freebsd.org/cgi/man.cgi?query=flock&sektion=2)
a WAL database can only be accessed by a single proccess.
Other processes that attempt to access a database locked with BSD locks,
will fail with the [`SQLITE_PROTOCOL`](https://sqlite.org/rescode.html#protocol) error code.
On Windows, this package may use `MapViewOfFile`, like SQLite. On Windows, this package may use `MapViewOfFile`, like SQLite.
You can also opt into a cross-platform, in-process, memory sharing implementation You can also opt into a cross-platform, in-process, memory sharing implementation

View file

@ -9,11 +9,11 @@
) )
func osGetSharedLock(file *os.File) _ErrorCode { func osGetSharedLock(file *os.File) _ErrorCode {
return osLock(file, unix.LOCK_SH|unix.LOCK_NB, _IOERR_RDLOCK) return osFlock(file, unix.LOCK_SH|unix.LOCK_NB, _IOERR_RDLOCK)
} }
func osGetReservedLock(file *os.File) _ErrorCode { func osGetReservedLock(file *os.File) _ErrorCode {
rc := osLock(file, unix.LOCK_EX|unix.LOCK_NB, _IOERR_LOCK) rc := osFlock(file, unix.LOCK_EX|unix.LOCK_NB, _IOERR_LOCK)
if rc == _BUSY { if rc == _BUSY {
// The documentation states that a lock is upgraded by // The documentation states that a lock is upgraded by
// releasing the previous lock, then acquiring the new lock. // releasing the previous lock, then acquiring the new lock.
@ -37,7 +37,7 @@ func osGetExclusiveLock(file *os.File, state *LockLevel) _ErrorCode {
} }
func osDowngradeLock(file *os.File, _ LockLevel) _ErrorCode { func osDowngradeLock(file *os.File, _ LockLevel) _ErrorCode {
rc := osLock(file, unix.LOCK_SH|unix.LOCK_NB, _IOERR_RDLOCK) rc := osFlock(file, unix.LOCK_SH|unix.LOCK_NB, _IOERR_RDLOCK)
if rc == _BUSY { if rc == _BUSY {
// The documentation states that a lock is downgraded by // The documentation states that a lock is downgraded by
// releasing the previous lock then acquiring the new lock. // releasing the previous lock then acquiring the new lock.
@ -66,7 +66,36 @@ func osCheckReservedLock(file *os.File) (bool, _ErrorCode) {
return lock == unix.F_WRLCK, rc return lock == unix.F_WRLCK, rc
} }
func osLock(file *os.File, how int, def _ErrorCode) _ErrorCode { func osFlock(file *os.File, how int, def _ErrorCode) _ErrorCode {
err := unix.Flock(int(file.Fd()), how) err := unix.Flock(int(file.Fd()), how)
return osLockErrorCode(err, def) return osLockErrorCode(err, def)
} }
func osReadLock(file *os.File, start, len int64) _ErrorCode {
return osLock(file, unix.F_RDLCK, start, len, _IOERR_RDLOCK)
}
func osWriteLock(file *os.File, start, len int64) _ErrorCode {
return osLock(file, unix.F_WRLCK, start, len, _IOERR_LOCK)
}
func osLock(file *os.File, typ int16, start, len int64, def _ErrorCode) _ErrorCode {
err := unix.FcntlFlock(file.Fd(), unix.F_SETLK, &unix.Flock_t{
Type: typ,
Start: start,
Len: len,
})
return osLockErrorCode(err, def)
}
func osUnlock(file *os.File, start, len int64) _ErrorCode {
err := unix.FcntlFlock(file.Fd(), unix.F_SETLK, &unix.Flock_t{
Type: unix.F_UNLCK,
Start: start,
Len: len,
})
if err != nil {
return _IOERR_UNLOCK
}
return _OK
}

View file

@ -56,16 +56,12 @@ func osAllocate(file *os.File, size int64) error {
return file.Truncate(size) return file.Truncate(size)
} }
func osUnlock(file *os.File, start, len int64) _ErrorCode { func osReadLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode {
err := unix.FcntlFlock(file.Fd(), _F_OFD_SETLK, &unix.Flock_t{ return osLock(file, unix.F_RDLCK, start, len, timeout, _IOERR_RDLOCK)
Type: unix.F_UNLCK, }
Start: start,
Len: len, func osWriteLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode {
}) return osLock(file, unix.F_WRLCK, start, len, timeout, _IOERR_LOCK)
if err != nil {
return _IOERR_UNLOCK
}
return _OK
} }
func osLock(file *os.File, typ int16, start, len int64, timeout time.Duration, def _ErrorCode) _ErrorCode { func osLock(file *os.File, typ int16, start, len int64, timeout time.Duration, def _ErrorCode) _ErrorCode {
@ -88,10 +84,14 @@ func osLock(file *os.File, typ int16, start, len int64, timeout time.Duration, d
return osLockErrorCode(err, def) return osLockErrorCode(err, def)
} }
func osReadLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode { func osUnlock(file *os.File, start, len int64) _ErrorCode {
return osLock(file, unix.F_RDLCK, start, len, timeout, _IOERR_RDLOCK) err := unix.FcntlFlock(file.Fd(), _F_OFD_SETLK, &unix.Flock_t{
} Type: unix.F_UNLCK,
Start: start,
func osWriteLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode { Len: len,
return osLock(file, unix.F_WRLCK, start, len, timeout, _IOERR_LOCK) })
if err != nil {
return _IOERR_UNLOCK
}
return _OK
} }

View file

@ -7,6 +7,8 @@
"io/fs" "io/fs"
"os" "os"
"sync" "sync"
"github.com/ncruces/go-sqlite3/internal/dotlk"
) )
var ( var (
@ -28,12 +30,10 @@ func osGetSharedLock(file *os.File) _ErrorCode {
name := file.Name() name := file.Name()
locker := vfsDotLocks[name] locker := vfsDotLocks[name]
if locker == nil { if locker == nil {
f, err := os.OpenFile(name+".lock", os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666) if err := dotlk.TryLock(name + ".lock"); err != nil {
f.Close() if errors.Is(err, fs.ErrExist) {
if errors.Is(err, fs.ErrExist) { return _BUSY // Another process has the lock.
return _BUSY // Another process has the lock. }
}
if err != nil {
return _IOERR_LOCK return _IOERR_LOCK
} }
locker = &vfsDotLocker{} locker = &vfsDotLocker{}
@ -114,8 +114,7 @@ func osReleaseLock(file *os.File, state LockLevel) _ErrorCode {
} }
if locker.shared == 1 { if locker.shared == 1 {
err := os.Remove(name + ".lock") if err := dotlk.Unlock(name + ".lock"); err != nil {
if err != nil && !errors.Is(err, fs.ErrNotExist) {
return _IOERR_UNLOCK return _IOERR_UNLOCK
} }
delete(vfsDotLocks, name) delete(vfsDotLocks, name)

View file

@ -3,7 +3,6 @@
package vfs package vfs
import ( import (
"math/rand"
"os" "os"
"time" "time"
@ -22,6 +21,30 @@ func osAllocate(file *os.File, size int64) error {
return unix.Fallocate(int(file.Fd()), 0, 0, size) return unix.Fallocate(int(file.Fd()), 0, 0, size)
} }
func osReadLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode {
return osLock(file, unix.F_RDLCK, start, len, timeout, _IOERR_RDLOCK)
}
func osWriteLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode {
return osLock(file, unix.F_WRLCK, start, len, timeout, _IOERR_LOCK)
}
func osLock(file *os.File, typ int16, start, len int64, timeout time.Duration, def _ErrorCode) _ErrorCode {
lock := unix.Flock_t{
Type: typ,
Start: start,
Len: len,
}
var err error
switch {
case timeout < 0:
err = unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLKW, &lock)
default:
err = unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &lock)
}
return osLockErrorCode(err, def)
}
func osUnlock(file *os.File, start, len int64) _ErrorCode { func osUnlock(file *os.File, start, len int64) _ErrorCode {
err := unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &unix.Flock_t{ err := unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &unix.Flock_t{
Type: unix.F_UNLCK, Type: unix.F_UNLCK,
@ -33,40 +56,3 @@ func osUnlock(file *os.File, start, len int64) _ErrorCode {
} }
return _OK return _OK
} }
func osLock(file *os.File, typ int16, start, len int64, timeout time.Duration, def _ErrorCode) _ErrorCode {
lock := unix.Flock_t{
Type: typ,
Start: start,
Len: len,
}
var err error
switch {
case timeout == 0:
err = unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &lock)
case timeout < 0:
err = unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLKW, &lock)
default:
before := time.Now()
for {
err = unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &lock)
if errno, _ := err.(unix.Errno); errno != unix.EAGAIN {
break
}
if time.Since(before) > timeout {
break
}
const sleepIncrement = 1024*1024 - 1 // power of two, ~1ms
time.Sleep(time.Duration(rand.Int63() & sleepIncrement))
}
}
return osLockErrorCode(err, def)
}
func osReadLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode {
return osLock(file, unix.F_RDLCK, start, len, timeout, _IOERR_RDLOCK)
}
func osWriteLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode {
return osLock(file, unix.F_WRLCK, start, len, timeout, _IOERR_LOCK)
}

View file

@ -45,6 +45,7 @@ func osGetExclusiveLock(file *os.File, state *LockLevel) _ErrorCode {
osUnlock(file, _SHARED_FIRST, _SHARED_SIZE) osUnlock(file, _SHARED_FIRST, _SHARED_SIZE)
// Acquire the EXCLUSIVE lock. // Acquire the EXCLUSIVE lock.
// Can't wait here, because the file is not OVERLAPPED.
rc := osWriteLock(file, _SHARED_FIRST, _SHARED_SIZE, 0) rc := osWriteLock(file, _SHARED_FIRST, _SHARED_SIZE, 0)
if rc != _OK { if rc != _OK {
@ -106,6 +107,27 @@ func osCheckReservedLock(file *os.File) (bool, _ErrorCode) {
return false, rc return false, rc
} }
func osReadLock(file *os.File, start, len uint32, timeout time.Duration) _ErrorCode {
return osLock(file, 0, start, len, timeout, _IOERR_RDLOCK)
}
func osWriteLock(file *os.File, start, len uint32, timeout time.Duration) _ErrorCode {
return osLock(file, windows.LOCKFILE_EXCLUSIVE_LOCK, start, len, timeout, _IOERR_LOCK)
}
func osLock(file *os.File, flags, start, len uint32, timeout time.Duration, def _ErrorCode) _ErrorCode {
var err error
switch {
case timeout == 0:
err = osLockEx(file, flags|windows.LOCKFILE_FAIL_IMMEDIATELY, start, len)
case timeout < 0:
err = osLockEx(file, flags, start, len)
default:
err = osLockExTimeout(file, flags, start, len, timeout)
}
return osLockErrorCode(err, def)
}
func osUnlock(file *os.File, start, len uint32) _ErrorCode { func osUnlock(file *os.File, start, len uint32) _ErrorCode {
err := windows.UnlockFileEx(windows.Handle(file.Fd()), err := windows.UnlockFileEx(windows.Handle(file.Fd()),
0, len, 0, &windows.Overlapped{Offset: start}) 0, len, 0, &windows.Overlapped{Offset: start})
@ -118,52 +140,40 @@ func osUnlock(file *os.File, start, len uint32) _ErrorCode {
return _OK return _OK
} }
func osLock(file *os.File, flags, start, len uint32, timeout time.Duration, def _ErrorCode) _ErrorCode { func osLockEx(file *os.File, flags, start, len uint32) error {
var err error
switch {
case timeout == 0:
err = osLockEx(file, flags|windows.LOCKFILE_FAIL_IMMEDIATELY, start, len, 0)
case timeout < 0:
err = osLockEx(file, flags, start, len, 0)
default:
var event windows.Handle
event, err = windows.CreateEvent(nil, 1, 0, nil)
if err != nil {
break
}
defer windows.CloseHandle(event)
err = osLockEx(file, flags, start, len, event)
if err == windows.ERROR_IO_PENDING {
rc, serr := windows.WaitForSingleObject(event, uint32(timeout/time.Millisecond))
if rc == windows.WAIT_OBJECT_0 {
return _OK
}
if serr != nil {
err = serr
} else {
err = windows.Errno(rc)
}
windows.CancelIo(windows.Handle(file.Fd()))
}
}
return osLockErrorCode(err, def)
}
func osLockEx(file *os.File, flags, start, len uint32, event windows.Handle) error {
return windows.LockFileEx(windows.Handle(file.Fd()), flags, return windows.LockFileEx(windows.Handle(file.Fd()), flags,
0, len, 0, &windows.Overlapped{ 0, len, 0, &windows.Overlapped{Offset: start})
Offset: start,
HEvent: event,
})
} }
func osReadLock(file *os.File, start, len uint32, timeout time.Duration) _ErrorCode { func osLockExTimeout(file *os.File, flags, start, len uint32, timeout time.Duration) error {
return osLock(file, 0, start, len, timeout, _IOERR_RDLOCK) event, err := windows.CreateEvent(nil, 1, 0, nil)
} if err != nil {
return err
}
defer windows.CloseHandle(event)
func osWriteLock(file *os.File, start, len uint32, timeout time.Duration) _ErrorCode { fd := windows.Handle(file.Fd())
return osLock(file, windows.LOCKFILE_EXCLUSIVE_LOCK, start, len, timeout, _IOERR_LOCK) overlapped := &windows.Overlapped{
Offset: start,
HEvent: event,
}
err = windows.LockFileEx(fd, flags, 0, len, 0, overlapped)
if err != windows.ERROR_IO_PENDING {
return err
}
ms := (timeout + time.Millisecond - 1) / time.Millisecond
rc, err := windows.WaitForSingleObject(event, uint32(ms))
if rc == windows.WAIT_OBJECT_0 {
return nil
}
defer windows.CancelIoEx(fd, overlapped)
if err != nil {
return err
}
return windows.Errno(rc)
} }
func osLockErrorCode(err error, def _ErrorCode) _ErrorCode { func osLockErrorCode(err error, def _ErrorCode) _ErrorCode {
@ -175,8 +185,8 @@ func osLockErrorCode(err error, def _ErrorCode) _ErrorCode {
switch errno { switch errno {
case case
windows.ERROR_LOCK_VIOLATION, windows.ERROR_LOCK_VIOLATION,
windows.ERROR_IO_PENDING,
windows.ERROR_OPERATION_ABORTED, windows.ERROR_OPERATION_ABORTED,
windows.ERROR_IO_PENDING,
windows.WAIT_TIMEOUT: windows.WAIT_TIMEOUT:
return _BUSY return _BUSY
} }

View file

@ -4,7 +4,9 @@
import ( import (
"context" "context"
"errors"
"io" "io"
"io/fs"
"os" "os"
"sync" "sync"
@ -71,23 +73,21 @@ func (s *vfsShm) shmOpen() _ErrorCode {
return _OK return _OK
} }
// Always open file read-write, as it will be shared. var f *os.File
f, err := os.OpenFile(s.path, // Close file on error.
os.O_RDWR|os.O_CREATE|_O_NOFOLLOW, 0666) // Keep this here to avoid confusing checklocks.
if err != nil {
return _CANTOPEN
}
// Closes file if it's not nil.
defer func() { f.Close() }() defer func() { f.Close() }()
fi, err := f.Stat()
if err != nil {
return _IOERR_FSTAT
}
vfsShmListMtx.Lock() vfsShmListMtx.Lock()
defer vfsShmListMtx.Unlock() defer vfsShmListMtx.Unlock()
// Stat file without opening it.
// Closing it would release all POSIX locks on it.
fi, err := os.Stat(s.path)
if err != nil && !errors.Is(err, fs.ErrNotExist) {
return _IOERR_FSTAT
}
// Find a shared file, increase the reference count. // Find a shared file, increase the reference count.
for _, g := range vfsShmList { for _, g := range vfsShmList {
if g != nil && os.SameFile(fi, g.info) { if g != nil && os.SameFile(fi, g.info) {
@ -97,13 +97,33 @@ func (s *vfsShm) shmOpen() _ErrorCode {
} }
} }
// Lock and truncate the file. // Always open file read-write, as it will be shared.
// The lock is only released by closing the file. f, err = os.OpenFile(s.path,
if rc := osLock(f, unix.LOCK_EX|unix.LOCK_NB, _IOERR_LOCK); rc != _OK { os.O_RDWR|os.O_CREATE|_O_NOFOLLOW, 0666)
if err != nil {
return _CANTOPEN
}
// Dead man's switch.
if lock, rc := osTestLock(f, _SHM_DMS, 1); rc != _OK {
return _IOERR_LOCK
} else if lock == unix.F_WRLCK {
return _BUSY
} else if lock == unix.F_UNLCK {
if rc := osWriteLock(f, _SHM_DMS, 1); rc != _OK {
return rc
}
if err := f.Truncate(0); err != nil {
return _IOERR_SHMOPEN
}
}
if rc := osReadLock(f, _SHM_DMS, 1); rc != _OK {
return rc return rc
} }
if err := f.Truncate(0); err != nil {
return _IOERR_SHMOPEN fi, err = f.Stat()
if err != nil {
return _IOERR_FSTAT
} }
// Add the new shared file. // Add the new shared file.
@ -157,7 +177,30 @@ func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, ext
func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) _ErrorCode { func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) _ErrorCode {
s.Lock() s.Lock()
defer s.Unlock() defer s.Unlock()
return s.shmMemLock(offset, n, flags)
// Check if we could obtain/release the lock locally.
rc := s.shmMemLock(offset, n, flags)
if rc != _OK {
return rc
}
// Obtain/release the appropriate file lock.
switch {
case flags&_SHM_UNLOCK != 0:
return osUnlock(s.File, _SHM_BASE+int64(offset), int64(n))
case flags&_SHM_SHARED != 0:
rc = osReadLock(s.File, _SHM_BASE+int64(offset), int64(n))
case flags&_SHM_EXCLUSIVE != 0:
rc = osWriteLock(s.File, _SHM_BASE+int64(offset), int64(n))
default:
panic(util.AssertErr())
}
// Release the local lock.
if rc != _OK {
s.shmMemLock(offset, n, flags^(_SHM_UNLOCK|_SHM_LOCK))
}
return rc
} }
func (s *vfsShm) shmUnmap(delete bool) { func (s *vfsShm) shmUnmap(delete bool) {

View file

@ -6,11 +6,11 @@
"context" "context"
"errors" "errors"
"io/fs" "io/fs"
"os"
"sync" "sync"
"github.com/tetratelabs/wazero/api" "github.com/tetratelabs/wazero/api"
"github.com/ncruces/go-sqlite3/internal/dotlk"
"github.com/ncruces/go-sqlite3/internal/util" "github.com/ncruces/go-sqlite3/internal/util"
) )
@ -58,8 +58,7 @@ func (s *vfsShm) Close() error {
return nil return nil
} }
err := os.Remove(s.path) if err := dotlk.Unlock(s.path); err != nil {
if err != nil && !errors.Is(err, fs.ErrNotExist) {
return _IOERR_UNLOCK return _IOERR_UNLOCK
} }
delete(vfsShmList, s.path) delete(vfsShmList, s.path)
@ -82,9 +81,8 @@ func (s *vfsShm) shmOpen() _ErrorCode {
return _OK return _OK
} }
// Create a directory on disk to ensure only this process // Dead man's switch.
// uses this path to register a shared memory. err := dotlk.LockShm(s.path)
err := os.Mkdir(s.path, 0777)
if errors.Is(err, fs.ErrExist) { if errors.Is(err, fs.ErrExist) {
return _BUSY return _BUSY
} }

5
vendor/modules.txt vendored
View file

@ -24,7 +24,7 @@ codeberg.org/gruf/go-fastcopy
# codeberg.org/gruf/go-fastpath/v2 v2.0.0 # codeberg.org/gruf/go-fastpath/v2 v2.0.0
## explicit; go 1.14 ## explicit; go 1.14
codeberg.org/gruf/go-fastpath/v2 codeberg.org/gruf/go-fastpath/v2
# codeberg.org/gruf/go-ffmpreg v0.6.2 # codeberg.org/gruf/go-ffmpreg v0.6.4
## explicit; go 1.22.0 ## explicit; go 1.22.0
codeberg.org/gruf/go-ffmpreg/embed codeberg.org/gruf/go-ffmpreg/embed
codeberg.org/gruf/go-ffmpreg/wasm codeberg.org/gruf/go-ffmpreg/wasm
@ -520,12 +520,13 @@ github.com/modern-go/reflect2
# github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 # github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822
## explicit ## explicit
github.com/munnerz/goautoneg github.com/munnerz/goautoneg
# github.com/ncruces/go-sqlite3 v0.21.0 # github.com/ncruces/go-sqlite3 v0.21.2
## explicit; go 1.21 ## explicit; go 1.21
github.com/ncruces/go-sqlite3 github.com/ncruces/go-sqlite3
github.com/ncruces/go-sqlite3/driver github.com/ncruces/go-sqlite3/driver
github.com/ncruces/go-sqlite3/embed github.com/ncruces/go-sqlite3/embed
github.com/ncruces/go-sqlite3/internal/alloc github.com/ncruces/go-sqlite3/internal/alloc
github.com/ncruces/go-sqlite3/internal/dotlk
github.com/ncruces/go-sqlite3/internal/util github.com/ncruces/go-sqlite3/internal/util
github.com/ncruces/go-sqlite3/util/osutil github.com/ncruces/go-sqlite3/util/osutil
github.com/ncruces/go-sqlite3/util/sql3util github.com/ncruces/go-sqlite3/util/sql3util

View file

@ -32,10 +32,16 @@
{{- range .stylesheets }} {{- range .stylesheets }}
<link rel="preload" href="{{- . -}}" as="style"> <link rel="preload" href="{{- . -}}" as="style">
{{- end }} {{- end }}
{{- if .instance.CustomCSS }}
<link rel="preload" href="/custom.css" as="style">
{{- end }}
<link rel="stylesheet" href="/assets/dist/_colors.css"> <link rel="stylesheet" href="/assets/dist/_colors.css">
<link rel="stylesheet" href="/assets/dist/base.css"> <link rel="stylesheet" href="/assets/dist/base.css">
<link rel="stylesheet" href="/assets/dist/page.css"> <link rel="stylesheet" href="/assets/dist/page.css">
{{- range .stylesheets }} {{- range .stylesheets }}
<link rel="stylesheet" href="{{- . -}}"> <link rel="stylesheet" href="{{- . -}}">
{{- end }} {{- end }}
{{- if .instance.CustomCSS }}
<link rel="stylesheet" href="/custom.css">
{{- end }}
{{- end }} {{- end }}