2021-08-29 15:41:41 +01:00
|
|
|
// Copyright 2021 The Sqlite Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package sqlite3
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"sync"
|
|
|
|
"sync/atomic"
|
|
|
|
"unsafe"
|
|
|
|
|
|
|
|
"modernc.org/libc"
|
|
|
|
"modernc.org/libc/sys/types"
|
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
tls := libc.NewTLS()
|
|
|
|
if Xsqlite3_threadsafe(tls) == 0 {
|
|
|
|
panic(fmt.Errorf("sqlite: thread safety configuration error"))
|
|
|
|
}
|
|
|
|
|
2021-12-12 14:47:51 +00:00
|
|
|
varArgs := libc.Xmalloc(tls, types.Size_t(unsafe.Sizeof(uintptr(0))))
|
|
|
|
if varArgs == 0 {
|
|
|
|
panic(fmt.Errorf("cannot allocate memory"))
|
2021-08-29 15:41:41 +01:00
|
|
|
}
|
|
|
|
|
2021-12-12 14:47:51 +00:00
|
|
|
// int sqlite3_config(int, ...);
|
|
|
|
if rc := Xsqlite3_config(tls, SQLITE_CONFIG_MUTEX, libc.VaList(varArgs, uintptr(unsafe.Pointer(&mutexMethods)))); rc != SQLITE_OK {
|
|
|
|
p := Xsqlite3_errstr(tls, rc)
|
|
|
|
str := libc.GoString(p)
|
|
|
|
panic(fmt.Errorf("sqlite: failed to configure mutex methods: %v", str))
|
|
|
|
}
|
|
|
|
|
|
|
|
libc.Xfree(tls, varArgs)
|
2021-08-29 15:41:41 +01:00
|
|
|
tls.Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
mutexMethods = Sqlite3_mutex_methods{
|
|
|
|
FxMutexInit: *(*uintptr)(unsafe.Pointer(&struct{ f func(*libc.TLS) int32 }{mutexInit})),
|
|
|
|
FxMutexEnd: *(*uintptr)(unsafe.Pointer(&struct{ f func(*libc.TLS) int32 }{mutexEnd})),
|
|
|
|
FxMutexAlloc: *(*uintptr)(unsafe.Pointer(&struct {
|
|
|
|
f func(*libc.TLS, int32) uintptr
|
|
|
|
}{mutexAlloc})),
|
|
|
|
FxMutexFree: *(*uintptr)(unsafe.Pointer(&struct{ f func(*libc.TLS, uintptr) }{mutexFree})),
|
|
|
|
FxMutexEnter: *(*uintptr)(unsafe.Pointer(&struct{ f func(*libc.TLS, uintptr) }{mutexEnter})),
|
|
|
|
FxMutexTry: *(*uintptr)(unsafe.Pointer(&struct {
|
|
|
|
f func(*libc.TLS, uintptr) int32
|
|
|
|
}{mutexTry})),
|
|
|
|
FxMutexLeave: *(*uintptr)(unsafe.Pointer(&struct{ f func(*libc.TLS, uintptr) }{mutexLeave})),
|
|
|
|
FxMutexHeld: *(*uintptr)(unsafe.Pointer(&struct {
|
|
|
|
f func(*libc.TLS, uintptr) int32
|
|
|
|
}{mutexHeld})),
|
|
|
|
FxMutexNotheld: *(*uintptr)(unsafe.Pointer(&struct {
|
|
|
|
f func(*libc.TLS, uintptr) int32
|
|
|
|
}{mutexNotheld})),
|
|
|
|
}
|
|
|
|
|
2023-01-22 11:26:47 +00:00
|
|
|
MutexCounters = libc.NewPerfCounter([]string{
|
|
|
|
"enter-fast",
|
|
|
|
"enter-recursive",
|
|
|
|
"enter-recursive-loop",
|
|
|
|
"try-fast",
|
|
|
|
"try-recursive",
|
|
|
|
})
|
|
|
|
MutexEnterCallers = libc.NewStackCapture(4)
|
|
|
|
|
|
|
|
mutexes mutexPool
|
|
|
|
|
|
|
|
mutexApp1 = mutexes.alloc(false)
|
|
|
|
mutexApp2 = mutexes.alloc(false)
|
|
|
|
mutexApp3 = mutexes.alloc(false)
|
|
|
|
mutexLRU = mutexes.alloc(false)
|
|
|
|
mutexMaster = mutexes.alloc(false)
|
|
|
|
mutexMem = mutexes.alloc(false)
|
|
|
|
mutexOpen = mutexes.alloc(false)
|
|
|
|
mutexPMem = mutexes.alloc(false)
|
|
|
|
mutexPRNG = mutexes.alloc(false)
|
|
|
|
mutexVFS1 = mutexes.alloc(false)
|
|
|
|
mutexVFS2 = mutexes.alloc(false)
|
|
|
|
mutexVFS3 = mutexes.alloc(false)
|
2021-08-29 15:41:41 +01:00
|
|
|
)
|
|
|
|
|
2023-01-22 11:26:47 +00:00
|
|
|
type mutexPool struct {
|
|
|
|
sync.Mutex
|
|
|
|
a []*[256]mutex
|
|
|
|
freeList []int
|
|
|
|
}
|
|
|
|
|
|
|
|
func mutexFromPtr(p uintptr) *mutex {
|
|
|
|
if p == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
2023-06-05 09:15:05 +01:00
|
|
|
|
2023-01-22 11:26:47 +00:00
|
|
|
ix := p - 1
|
2023-06-05 09:15:05 +01:00
|
|
|
|
|
|
|
mutexes.Lock()
|
|
|
|
defer mutexes.Unlock()
|
|
|
|
|
2023-01-22 11:26:47 +00:00
|
|
|
return &mutexes.a[ix>>8][ix&255]
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *mutexPool) alloc(recursive bool) uintptr {
|
|
|
|
m.Lock()
|
|
|
|
defer m.Unlock()
|
|
|
|
|
|
|
|
n := len(m.freeList)
|
|
|
|
if n == 0 {
|
|
|
|
outer := len(m.a) << 8
|
|
|
|
m.a = append(m.a, &[256]mutex{})
|
|
|
|
for i := 0; i < 256; i++ {
|
|
|
|
m.freeList = append(m.freeList, outer+i)
|
|
|
|
}
|
|
|
|
n = len(m.freeList)
|
|
|
|
}
|
|
|
|
ix := m.freeList[n-1]
|
|
|
|
outer := ix >> 8
|
|
|
|
inner := ix & 255
|
|
|
|
m.freeList = m.freeList[:n-1]
|
|
|
|
p := &m.a[outer][inner]
|
|
|
|
p.poolIndex = ix
|
|
|
|
p.recursive = recursive
|
|
|
|
return uintptr(ix) + 1
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *mutexPool) free(p uintptr) {
|
|
|
|
ptr := mutexFromPtr(p)
|
|
|
|
ix := ptr.poolIndex
|
|
|
|
*ptr = mutex{}
|
|
|
|
|
2023-06-05 09:15:05 +01:00
|
|
|
m.Lock()
|
2023-01-22 11:26:47 +00:00
|
|
|
defer m.Unlock()
|
|
|
|
|
|
|
|
m.freeList = append(m.freeList, ix)
|
|
|
|
}
|
|
|
|
|
2021-08-29 15:41:41 +01:00
|
|
|
type mutex struct {
|
2023-01-22 11:26:47 +00:00
|
|
|
sync.Mutex
|
|
|
|
wait sync.Mutex
|
|
|
|
|
|
|
|
poolIndex int
|
|
|
|
|
2021-08-29 15:41:41 +01:00
|
|
|
cnt int32
|
|
|
|
id int32
|
2023-01-22 11:26:47 +00:00
|
|
|
|
2021-08-29 15:41:41 +01:00
|
|
|
recursive bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *mutex) enter(id int32) {
|
2023-01-22 11:26:47 +00:00
|
|
|
// MutexEnterCallers.Record()
|
2021-08-29 15:41:41 +01:00
|
|
|
if !m.recursive {
|
2023-01-22 11:26:47 +00:00
|
|
|
// MutexCounters.Inc(0)
|
2021-08-29 15:41:41 +01:00
|
|
|
m.Lock()
|
|
|
|
m.id = id
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-01-22 11:26:47 +00:00
|
|
|
// MutexCounters.Inc(1)
|
2021-08-29 15:41:41 +01:00
|
|
|
for {
|
|
|
|
m.Lock()
|
|
|
|
switch m.id {
|
|
|
|
case 0:
|
|
|
|
m.cnt = 1
|
|
|
|
m.id = id
|
|
|
|
m.wait.Lock()
|
|
|
|
m.Unlock()
|
|
|
|
return
|
|
|
|
case id:
|
|
|
|
m.cnt++
|
|
|
|
m.Unlock()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-01-22 11:26:47 +00:00
|
|
|
// MutexCounters.Inc(2)
|
2021-08-29 15:41:41 +01:00
|
|
|
m.Unlock()
|
|
|
|
m.wait.Lock()
|
|
|
|
//lint:ignore SA2001 TODO report staticcheck issue
|
|
|
|
m.wait.Unlock()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *mutex) try(id int32) int32 {
|
|
|
|
if !m.recursive {
|
2023-01-22 11:26:47 +00:00
|
|
|
// MutexCounters.Inc(3)
|
2021-08-29 15:41:41 +01:00
|
|
|
return SQLITE_BUSY
|
|
|
|
}
|
|
|
|
|
2023-01-22 11:26:47 +00:00
|
|
|
// MutexCounters.Inc(4)
|
2021-08-29 15:41:41 +01:00
|
|
|
m.Lock()
|
|
|
|
switch m.id {
|
|
|
|
case 0:
|
|
|
|
m.cnt = 1
|
|
|
|
m.id = id
|
|
|
|
m.wait.Lock()
|
|
|
|
m.Unlock()
|
|
|
|
return SQLITE_OK
|
|
|
|
case id:
|
|
|
|
m.cnt++
|
|
|
|
m.Unlock()
|
|
|
|
return SQLITE_OK
|
|
|
|
}
|
|
|
|
|
|
|
|
m.Unlock()
|
|
|
|
return SQLITE_BUSY
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *mutex) leave(id int32) {
|
|
|
|
if !m.recursive {
|
|
|
|
m.id = 0
|
|
|
|
m.Unlock()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
m.Lock()
|
|
|
|
m.cnt--
|
|
|
|
if m.cnt == 0 {
|
|
|
|
m.id = 0
|
|
|
|
m.wait.Unlock()
|
|
|
|
}
|
|
|
|
m.Unlock()
|
|
|
|
}
|
|
|
|
|
|
|
|
// int (*xMutexInit)(void);
|
|
|
|
//
|
|
|
|
// The xMutexInit method defined by this structure is invoked as part of system
|
|
|
|
// initialization by the sqlite3_initialize() function. The xMutexInit routine
|
|
|
|
// is called by SQLite exactly once for each effective call to
|
|
|
|
// sqlite3_initialize().
|
|
|
|
//
|
|
|
|
// The xMutexInit() method must be threadsafe. It must be harmless to invoke
|
|
|
|
// xMutexInit() multiple times within the same process and without intervening
|
|
|
|
// calls to xMutexEnd(). Second and subsequent calls to xMutexInit() must be
|
|
|
|
// no-ops. xMutexInit() must not use SQLite memory allocation (sqlite3_malloc()
|
|
|
|
// and its associates).
|
|
|
|
//
|
|
|
|
// If xMutexInit fails in any way, it is expected to clean up after itself
|
|
|
|
// prior to returning.
|
|
|
|
func mutexInit(tls *libc.TLS) int32 { return SQLITE_OK }
|
|
|
|
|
|
|
|
// int (*xMutexEnd)(void);
|
|
|
|
func mutexEnd(tls *libc.TLS) int32 { return SQLITE_OK }
|
|
|
|
|
|
|
|
// sqlite3_mutex *(*xMutexAlloc)(int);
|
|
|
|
//
|
|
|
|
// The sqlite3_mutex_alloc() routine allocates a new mutex and returns a
|
|
|
|
// pointer to it. The sqlite3_mutex_alloc() routine returns NULL if it is
|
|
|
|
// unable to allocate the requested mutex. The argument to
|
|
|
|
// sqlite3_mutex_alloc() must one of these integer constants:
|
|
|
|
//
|
|
|
|
// SQLITE_MUTEX_FAST
|
|
|
|
// SQLITE_MUTEX_RECURSIVE
|
|
|
|
// SQLITE_MUTEX_STATIC_MASTER
|
|
|
|
// SQLITE_MUTEX_STATIC_MEM
|
|
|
|
// SQLITE_MUTEX_STATIC_OPEN
|
|
|
|
// SQLITE_MUTEX_STATIC_PRNG
|
|
|
|
// SQLITE_MUTEX_STATIC_LRU
|
|
|
|
// SQLITE_MUTEX_STATIC_PMEM
|
|
|
|
// SQLITE_MUTEX_STATIC_APP1
|
|
|
|
// SQLITE_MUTEX_STATIC_APP2
|
|
|
|
// SQLITE_MUTEX_STATIC_APP3
|
|
|
|
// SQLITE_MUTEX_STATIC_VFS1
|
|
|
|
// SQLITE_MUTEX_STATIC_VFS2
|
|
|
|
// SQLITE_MUTEX_STATIC_VFS3
|
|
|
|
//
|
|
|
|
// The first two constants (SQLITE_MUTEX_FAST and SQLITE_MUTEX_RECURSIVE) cause
|
|
|
|
// sqlite3_mutex_alloc() to create a new mutex. The new mutex is recursive when
|
|
|
|
// SQLITE_MUTEX_RECURSIVE is used but not necessarily so when SQLITE_MUTEX_FAST
|
|
|
|
// is used. The mutex implementation does not need to make a distinction
|
|
|
|
// between SQLITE_MUTEX_RECURSIVE and SQLITE_MUTEX_FAST if it does not want to.
|
|
|
|
// SQLite will only request a recursive mutex in cases where it really needs
|
|
|
|
// one. If a faster non-recursive mutex implementation is available on the host
|
|
|
|
// platform, the mutex subsystem might return such a mutex in response to
|
|
|
|
// SQLITE_MUTEX_FAST.
|
|
|
|
//
|
|
|
|
// The other allowed parameters to sqlite3_mutex_alloc() (anything other than
|
|
|
|
// SQLITE_MUTEX_FAST and SQLITE_MUTEX_RECURSIVE) each return a pointer to a
|
|
|
|
// static preexisting mutex. Nine static mutexes are used by the current
|
|
|
|
// version of SQLite. Future versions of SQLite may add additional static
|
|
|
|
// mutexes. Static mutexes are for internal use by SQLite only. Applications
|
|
|
|
// that use SQLite mutexes should use only the dynamic mutexes returned by
|
|
|
|
// SQLITE_MUTEX_FAST or SQLITE_MUTEX_RECURSIVE.
|
|
|
|
//
|
|
|
|
// Note that if one of the dynamic mutex parameters (SQLITE_MUTEX_FAST or
|
|
|
|
// SQLITE_MUTEX_RECURSIVE) is used then sqlite3_mutex_alloc() returns a
|
|
|
|
// different mutex on every call. For the static mutex types, the same mutex is
|
|
|
|
// returned on every call that has the same type number.
|
|
|
|
func mutexAlloc(tls *libc.TLS, typ int32) uintptr {
|
|
|
|
defer func() {
|
|
|
|
}()
|
|
|
|
switch typ {
|
|
|
|
case SQLITE_MUTEX_FAST:
|
2023-01-22 11:26:47 +00:00
|
|
|
return mutexes.alloc(false)
|
2021-08-29 15:41:41 +01:00
|
|
|
case SQLITE_MUTEX_RECURSIVE:
|
2023-01-22 11:26:47 +00:00
|
|
|
return mutexes.alloc(true)
|
2021-08-29 15:41:41 +01:00
|
|
|
case SQLITE_MUTEX_STATIC_MASTER:
|
2023-01-22 11:26:47 +00:00
|
|
|
return mutexMaster
|
2021-08-29 15:41:41 +01:00
|
|
|
case SQLITE_MUTEX_STATIC_MEM:
|
2023-01-22 11:26:47 +00:00
|
|
|
return mutexMem
|
2021-08-29 15:41:41 +01:00
|
|
|
case SQLITE_MUTEX_STATIC_OPEN:
|
2023-01-22 11:26:47 +00:00
|
|
|
return mutexOpen
|
2021-08-29 15:41:41 +01:00
|
|
|
case SQLITE_MUTEX_STATIC_PRNG:
|
2023-01-22 11:26:47 +00:00
|
|
|
return mutexPRNG
|
2021-08-29 15:41:41 +01:00
|
|
|
case SQLITE_MUTEX_STATIC_LRU:
|
2023-01-22 11:26:47 +00:00
|
|
|
return mutexLRU
|
2021-08-29 15:41:41 +01:00
|
|
|
case SQLITE_MUTEX_STATIC_PMEM:
|
2023-01-22 11:26:47 +00:00
|
|
|
return mutexPMem
|
2021-08-29 15:41:41 +01:00
|
|
|
case SQLITE_MUTEX_STATIC_APP1:
|
2023-01-22 11:26:47 +00:00
|
|
|
return mutexApp1
|
2021-08-29 15:41:41 +01:00
|
|
|
case SQLITE_MUTEX_STATIC_APP2:
|
2023-01-22 11:26:47 +00:00
|
|
|
return mutexApp2
|
2021-08-29 15:41:41 +01:00
|
|
|
case SQLITE_MUTEX_STATIC_APP3:
|
2023-01-22 11:26:47 +00:00
|
|
|
return mutexApp3
|
2021-08-29 15:41:41 +01:00
|
|
|
case SQLITE_MUTEX_STATIC_VFS1:
|
2023-01-22 11:26:47 +00:00
|
|
|
return mutexVFS1
|
2021-08-29 15:41:41 +01:00
|
|
|
case SQLITE_MUTEX_STATIC_VFS2:
|
2023-01-22 11:26:47 +00:00
|
|
|
return mutexVFS2
|
2021-08-29 15:41:41 +01:00
|
|
|
case SQLITE_MUTEX_STATIC_VFS3:
|
2023-01-22 11:26:47 +00:00
|
|
|
return mutexVFS3
|
2021-08-29 15:41:41 +01:00
|
|
|
default:
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// void (*xMutexFree)(sqlite3_mutex *);
|
2023-01-22 11:26:47 +00:00
|
|
|
func mutexFree(tls *libc.TLS, m uintptr) { mutexes.free(m) }
|
2021-08-29 15:41:41 +01:00
|
|
|
|
|
|
|
// The sqlite3_mutex_enter() and sqlite3_mutex_try() routines attempt to enter
|
|
|
|
// a mutex. If another thread is already within the mutex,
|
|
|
|
// sqlite3_mutex_enter() will block and sqlite3_mutex_try() will return
|
|
|
|
// SQLITE_BUSY. The sqlite3_mutex_try() interface returns SQLITE_OK upon
|
|
|
|
// successful entry. Mutexes created using SQLITE_MUTEX_RECURSIVE can be
|
|
|
|
// entered multiple times by the same thread. In such cases, the mutex must be
|
|
|
|
// exited an equal number of times before another thread can enter. If the same
|
|
|
|
// thread tries to enter any mutex other than an SQLITE_MUTEX_RECURSIVE more
|
|
|
|
// than once, the behavior is undefined.
|
|
|
|
//
|
|
|
|
// If the argument to sqlite3_mutex_enter(), sqlite3_mutex_try(), or
|
|
|
|
// sqlite3_mutex_leave() is a NULL pointer, then all three routines behave as
|
|
|
|
// no-ops.
|
|
|
|
|
|
|
|
// void (*xMutexEnter)(sqlite3_mutex *);
|
|
|
|
func mutexEnter(tls *libc.TLS, m uintptr) {
|
|
|
|
if m == 0 {
|
|
|
|
return
|
|
|
|
}
|
2023-01-22 11:26:47 +00:00
|
|
|
mutexFromPtr(m).enter(tls.ID)
|
2021-08-29 15:41:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// int (*xMutexTry)(sqlite3_mutex *);
|
|
|
|
func mutexTry(tls *libc.TLS, m uintptr) int32 {
|
|
|
|
if m == 0 {
|
|
|
|
return SQLITE_OK
|
|
|
|
}
|
|
|
|
|
2023-01-22 11:26:47 +00:00
|
|
|
return mutexFromPtr(m).try(tls.ID)
|
2021-08-29 15:41:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// void (*xMutexLeave)(sqlite3_mutex *);
|
|
|
|
func mutexLeave(tls *libc.TLS, m uintptr) {
|
|
|
|
if m == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-01-22 11:26:47 +00:00
|
|
|
mutexFromPtr(m).leave(tls.ID)
|
2021-08-29 15:41:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// The sqlite3_mutex_held() and sqlite3_mutex_notheld() routines are intended
|
|
|
|
// for use inside assert() statements. The SQLite core never uses these
|
|
|
|
// routines except inside an assert() and applications are advised to follow
|
|
|
|
// the lead of the core. The SQLite core only provides implementations for
|
|
|
|
// these routines when it is compiled with the SQLITE_DEBUG flag. External
|
|
|
|
// mutex implementations are only required to provide these routines if
|
|
|
|
// SQLITE_DEBUG is defined and if NDEBUG is not defined.
|
|
|
|
//
|
|
|
|
// These routines should return true if the mutex in their argument is held or
|
|
|
|
// not held, respectively, by the calling thread.
|
|
|
|
//
|
|
|
|
// The implementation is not required to provide versions of these routines
|
|
|
|
// that actually work. If the implementation does not provide working versions
|
|
|
|
// of these routines, it should at least provide stubs that always return true
|
|
|
|
// so that one does not get spurious assertion failures.
|
|
|
|
//
|
|
|
|
// If the argument to sqlite3_mutex_held() is a NULL pointer then the routine
|
|
|
|
// should return 1. This seems counter-intuitive since clearly the mutex cannot
|
|
|
|
// be held if it does not exist. But the reason the mutex does not exist is
|
|
|
|
// because the build is not using mutexes. And we do not want the assert()
|
|
|
|
// containing the call to sqlite3_mutex_held() to fail, so a non-zero return is
|
|
|
|
// the appropriate thing to do. The sqlite3_mutex_notheld() interface should
|
|
|
|
// also return 1 when given a NULL pointer.
|
|
|
|
|
|
|
|
// int (*xMutexHeld)(sqlite3_mutex *);
|
|
|
|
func mutexHeld(tls *libc.TLS, m uintptr) int32 {
|
|
|
|
if m == 0 {
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
2023-01-22 11:26:47 +00:00
|
|
|
return libc.Bool32(atomic.LoadInt32(&mutexFromPtr(m).id) == tls.ID)
|
2021-08-29 15:41:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// int (*xMutexNotheld)(sqlite3_mutex *);
|
|
|
|
func mutexNotheld(tls *libc.TLS, m uintptr) int32 {
|
|
|
|
if m == 0 {
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
2023-01-22 11:26:47 +00:00
|
|
|
return libc.Bool32(atomic.LoadInt32(&mutexFromPtr(m).id) != tls.ID)
|
2021-08-29 15:41:41 +01:00
|
|
|
}
|