log: improve the most common use case (#19242)

This commit is contained in:
Delyan Angelov 2023-08-31 06:44:11 +03:00 committed by GitHub
parent 55575fd7bd
commit 6fb4a481f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 576 additions and 150 deletions

View File

@ -1,6 +1,14 @@
import log
fn main() {
// Note: you *do not* need to create a logger instance, and pass it around, just to use the `log` module.
// The log module already creates an instance of a thread safe Logger, and utility functions to work with it:
log.set_level(.debug)
log.debug('simple debug message')
log.warn('simple warning message')
log.info('simple information message')
log.error('simple error message')
mut l := log.Log{}
l.set_level(.info)
// Make a new file called info.log in the current folder

View File

@ -3,3 +3,48 @@
`log` provides your application logging services.
You can log to file or to the console and use different
logging levels, so that you are not overwhelmed by the logs.
## Basic usage:
The log module creates a default Log instance by default, and
provides utility functions, that you can use to access it.
Note: the default Log instance is thread safe.
That makes it very convenient to use in subsystems, without having
to thread a log instance everywhere:
```v
import log
fn abc() {
log.info('some information')
log.warn('a warning')
}
// this will not be visible, the default log level is .info:
log.debug('a debug message')
log.set_level(.debug)
// this will be now visible, the log level was changed to .debug:
log.debug('a debug message')
abc()
```
## Advanced usage:
You can also create your own log instances, with different options
applied to them:
```v
import log
fn main() {
mut l := log.Log{}
l.set_level(.info)
l.set_full_logpath('./info.log')
l.log_to_console_too()
l.info('info')
l.warn('warn')
l.error('error')
l.fatal('fatal') // panic, marked as [noreturn]
}
```

69
vlib/log/common.v Normal file
View File

@ -0,0 +1,69 @@
module log
import term
// Level defines the possible log levels, used by Log.set_level()
pub enum Level {
disabled = 0 // lowest level, disables everything else
fatal // disables error, warn, info and debug
error // disables warn, info and debug
warn // disables info and debug
info // disables debug
debug
}
// LogTarget defines the possible log targets, that Log supports
pub enum LogTarget {
console
file
both
}
// tag_to_cli returns the tag for log level `l` as a colored string.
fn tag_to_cli(l Level) string {
return match l {
.disabled { '' }
.fatal { term.red('FATAL') }
.error { term.red('ERROR') }
.warn { term.yellow('WARN ') }
.info { term.white('INFO ') }
.debug { term.magenta('DEBUG') }
}
}
// tag_to_file returns the tag for log level `l` as a string.
fn tag_to_file(l Level) string {
return match l {
.disabled { ' ' }
.fatal { 'FATAL' }
.error { 'ERROR' }
.warn { 'WARN ' }
.info { 'INFO ' }
.debug { 'DEBUG' }
}
}
// level_from_tag returns the log level from the given string.
// It returns `none` when it does not find a match.
pub fn level_from_tag(tag string) ?Level {
return match tag {
'DISABLED' { Level.disabled }
'FATAL' { Level.fatal }
'ERROR' { Level.error }
'WARN' { Level.warn }
'INFO' { Level.info }
'DEBUG' { Level.debug }
else { none }
}
}
// target_from_label returns the log target from the given string.
// It returns `none` when it does not find a match.
pub fn target_from_label(label string) ?LogTarget {
return match label {
'console' { LogTarget.console }
'file' { LogTarget.file }
'both' { LogTarget.both }
else { none }
}
}

42
vlib/log/default.c.v Normal file
View File

@ -0,0 +1,42 @@
// Copyright (c) 2019-2023 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
[has_globals]
module log
__global default_logger &Logger
// TODO: remove this hack, when the language has a way to access the raw pointer to an interface value directly:
[typedef]
struct C.log__Logger {
mut:
_object voidptr
}
// init will be called before the user's main program starts, to initialize the default logger
fn init() {
default_logger = new_thread_safe_log()
C.atexit(deinit)
}
// deinit will be called on exit of the program and will free the memory allocated for the default logger
fn deinit() {
free_logger(default_logger)
}
[manualfree]
fn free_logger(logger &Logger) {
if voidptr(logger) == unsafe { nil } {
return
}
unsafe {
// C.printf(c'free_logger logger: %p\n', logger)
logger.free()
//
pobject := &C.log__Logger(logger)._object
// C.printf(c'free_logger pobject: %p\n', pobject)
free(pobject)
//
free(logger)
}
}

55
vlib/log/default.v Normal file
View File

@ -0,0 +1,55 @@
// Copyright (c) 2019-2023 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module log
// set_logger changes the default logger instance to the one provided by the user.
// The existing logger will be freed, *after* the change is done.
[manualfree]
pub fn set_logger(logger &Logger) {
// C.printf(c"set_logger logger: %p | old logger: %p\n", logger, default_logger)
old_logger := unsafe { default_logger }
default_logger = unsafe { logger }
free_logger(old_logger)
}
// get_logger returns a pointer to the current default logger instance
[unsafe]
pub fn get_logger() &Logger {
return default_logger
}
// get_level returns the log level of the default Logger instance
pub fn get_level() Level {
return default_logger.get_level()
}
// set_level changes the log level of the default Logger instance
pub fn set_level(level Level) {
default_logger.set_level(level)
}
// fatal logs a `fatal` message, using the default Logger instance
pub fn fatal(s string) {
default_logger.fatal(s)
}
// error logs an `error` message, using the default Logger instance
pub fn error(s string) {
default_logger.error(s)
}
// error logs a `warning` message, using the default Logger instance
pub fn warn(s string) {
default_logger.warn(s)
}
// info logs an `info` message, using the default Logger instance
pub fn info(s string) {
default_logger.info(s)
}
// debug logs a `debug` message, using the default Logger instance
pub fn debug(s string) {
default_logger.debug(s)
}

37
vlib/log/default_test.v Normal file
View File

@ -0,0 +1,37 @@
import log
import time
fn test_default_log_instance() {
println(@FN + ' start')
log.info('info')
log.warn('warn')
log.error('error')
log.debug('no output for debug')
println('^^^ there should be no `no output for debug` shown above')
log.set_level(.debug)
log.debug('debug now')
println('^^^ there should be `debug now` shown above')
log.set_level(log.level_from_tag('INFO') or { log.Level.disabled })
log.info('info again')
log.set_level(log.level_from_tag('') or { log.Level.disabled })
log.error('no output anymore')
println('^^^ there should be no `no output anymore` shown above')
println(@FN + ' end')
}
fn log_messages(tidx int) {
time.sleep(2 * time.millisecond)
for i in 0 .. 3 {
log.debug('hi from thread ${tidx}')
}
}
fn test_default_log_instance_used_in_multiple_threads() {
eprintln('\n${@METHOD} start')
log.set_level(.debug)
mut threads := []thread{}
for tidx in 0 .. 3 {
threads << spawn log_messages(tidx)
}
threads.wait()
}

View File

@ -5,82 +5,6 @@ module log
import os
import time
import term
// Level defines possible log levels used by `Log`
pub enum Level {
disabled = 0
fatal
error
warn
info
debug
}
// LogTarget defines possible log targets
pub enum LogTarget {
console
file
both
}
// tag_to_cli returns the tag for log level `l` as a colored string.
fn tag_to_cli(l Level) string {
return match l {
.disabled { '' }
.fatal { term.red('FATAL') }
.error { term.red('ERROR') }
.warn { term.yellow('WARN ') }
.info { term.white('INFO ') }
.debug { term.blue('DEBUG') }
}
}
// tag_to_file returns the tag for log level `l` as a string.
fn tag_to_file(l Level) string {
return match l {
.disabled { ' ' }
.fatal { 'FATAL' }
.error { 'ERROR' }
.warn { 'WARN ' }
.info { 'INFO ' }
.debug { 'DEBUG' }
}
}
// level_from_tag returns the log level from the given string if it matches.
pub fn level_from_tag(tag string) ?Level {
return match tag {
'DISABLED' { Level.disabled }
'FATAL' { Level.fatal }
'ERROR' { Level.error }
'WARN' { Level.warn }
'INFO' { Level.info }
'DEBUG' { Level.debug }
else { none }
}
}
// target_from_label returns the log target from the given string if it matches.
pub fn target_from_label(label string) ?LogTarget {
return match label {
'console' { LogTarget.console }
'file' { LogTarget.file }
'both' { LogTarget.both }
else { none }
}
}
// Logger is an interface that describes a generic Logger
pub interface Logger {
mut:
fatal(s string)
error(s string)
warn(s string)
info(s string)
debug(s string)
set_level(level Level)
}
// Log represents a logging object
pub struct Log {
@ -94,16 +18,20 @@ pub mut:
}
// get_level gets the internal logging level.
pub fn (mut l Log) get_level() Level {
pub fn (l &Log) get_level() Level {
return l.level
}
// set_level sets the internal logging to `level`.
// set_level sets the logging level to `level`. Messges for levels above it will skipped.
// For example, after calling log.set_level(.info), log.debug('message') will produce nothing.
// Call log.set_level(.disabled) to turn off the logging of all messages.
pub fn (mut l Log) set_level(level Level) {
l.level = level
}
// set_output_level sets the internal logging output to `level`.
[deprecated: 'use .set_level(level) instead']
[deprecated_after: '2023-09-30']
pub fn (mut l Log) set_output_level(level Level) {
l.level = level
}
@ -154,14 +82,14 @@ pub fn (mut l Log) close() {
// log_file writes log line `s` with `level` to the log file.
fn (mut l Log) log_file(s string, level Level) {
timestamp := time.now().format_ss()
timestamp := time.now().format_ss_micro()
e := tag_to_file(level)
l.ofile.writeln('${timestamp} [${e}] ${s}') or { panic(err) }
}
// log_cli writes log line `s` with `level` to stdout.
fn (l &Log) log_cli(s string, level Level) {
timestamp := time.now().format_ss()
timestamp := time.now().format_ss_micro()
e := tag_to_cli(level)
println('${timestamp} [${e}] ${s}')
}
@ -219,3 +147,13 @@ pub fn (mut l Log) debug(s string) {
}
l.send_output(s, .debug)
}
// free frees the given Log instance
[unsafe]
pub fn (mut f Log) free() {
unsafe {
f.output_label.free()
f.ofile.close()
f.output_file_name.free()
}
}

View File

@ -1,7 +1,5 @@
module log
import time
fn log_mutable_statements(mut log Log) {
println(@FN + ' start')
log.info('info')
@ -30,10 +28,6 @@ fn logger_mutable_statements(mut log Logger) {
println(@FN + ' end')
}
fn delay() {
time.sleep(1 * time.second)
}
// Note that Log and Logger methods requires a mutable instance
// new_log create and return a new Log reference
@ -63,8 +57,8 @@ fn test_log_mutable_reference() {
println(@FN + ' start')
mut log := new_log()
assert typeof(log).name == '&log.Log'
spawn log_mutable_statements(mut log)
delay() // wait to finish
t := spawn log_mutable_statements(mut log)
t.wait()
assert true
println(@FN + ' end')
}
@ -75,8 +69,8 @@ fn test_logger_mutable_reference() {
mut logger := new_log_as_logger()
logger.set_level(.warn)
assert typeof(logger).name == '&log.Logger'
spawn logger_mutable_statements(mut logger)
delay() // wait to finish
t := spawn logger_mutable_statements(mut logger)
t.wait()
assert true
println(@FN + ' end')
}

View File

@ -0,0 +1,15 @@
module log
// Logger is an interface that describes a generic Logger
pub interface Logger {
get_level() Level
mut:
fatal(s string)
error(s string)
warn(s string)
info(s string)
debug(s string)
// utility methods:
set_level(level Level)
free()
}

79
vlib/log/safe_log.v Normal file
View File

@ -0,0 +1,79 @@
module log
import sync
// ThreadSafeLog embeds Log, and adds a mutex field.
// It uses the mutex to synchronise accesses to the embedded Log.
pub struct ThreadSafeLog {
Log
pub mut:
mu sync.Mutex
}
// new_thread_safe_log returns a new log structure, whose methods are safe
// to call by multiple threads.
pub fn new_thread_safe_log() &ThreadSafeLog {
mut x := &ThreadSafeLog{
level: .info
}
x.mu.init()
return x
}
// free frees the given ThreadSafeLog instance.
[unsafe]
pub fn (mut x ThreadSafeLog) free() {
unsafe {
x.Log.free()
x.mu.destroy()
// C.printf(c'ThreadSafeLog free(x), x: %p\n', x)
}
}
// set_level changes the log level
pub fn (mut x ThreadSafeLog) set_level(level Level) {
x.mu.@lock()
x.Log.set_level(level)
x.mu.unlock()
}
// debug logs a debug message
pub fn (mut x ThreadSafeLog) debug(s string) {
x.mu.@lock()
x.Log.debug(s)
x.mu.unlock()
}
// info logs an info messagep
pub fn (mut x ThreadSafeLog) info(s string) {
x.mu.@lock()
x.Log.info(s)
x.mu.unlock()
}
// warn logs a warning message
pub fn (mut x ThreadSafeLog) warn(s string) {
x.mu.@lock()
x.Log.warn(s)
x.mu.unlock()
}
// error logs an error message
pub fn (mut x ThreadSafeLog) error(s string) {
x.mu.@lock()
x.Log.error(s)
x.mu.unlock()
}
// fatal logs a fatal message, and panics
[noreturn]
pub fn (mut x ThreadSafeLog) fatal(s string) {
x.mu.@lock()
defer {
// TODO: Log.fatal() is marked as noreturn, but this defer is allowed.
// Think whether it should be, or if it should be a compiler notice at least,
// since it would not be reached at all (.fatal() panics).
x.mu.unlock()
}
x.Log.fatal(s)
}

11
vlib/sync/common_mutex.v Normal file
View File

@ -0,0 +1,11 @@
module sync
// str returns a string representation of the Mutex pointer
pub fn (m &Mutex) str() string {
return 'Mutex(${voidptr(m)})'
}
// str returns a string representation of the RwMutex pointer
pub fn (m &RwMutex) str() string {
return 'RwMutex(${voidptr(m)})'
}

11
vlib/sync/sync.c.v Normal file
View File

@ -0,0 +1,11 @@
module sync
[noreturn]
fn cpanic(res int) {
panic(unsafe { tos_clone(&u8(C.strerror(res))) })
}
[noreturn]
fn cpanic_errno() {
cpanic(C.errno)
}

View File

@ -21,6 +21,7 @@ fn C.pthread_rwlock_init(voidptr, voidptr) int
fn C.pthread_rwlock_rdlock(voidptr) int
fn C.pthread_rwlock_wrlock(voidptr) int
fn C.pthread_rwlock_unlock(voidptr) int
fn C.pthread_rwlock_destroy(voidptr) int
fn C.pthread_condattr_init(voidptr) int
fn C.pthread_condattr_setpshared(voidptr, int) int
fn C.pthread_condattr_destroy(voidptr) int
@ -76,22 +77,29 @@ mut:
count u32
}
// new_mutex creates and initialises a new mutex instance on the heap, then returns a pointer to it.
pub fn new_mutex() &Mutex {
mut m := &Mutex{}
m.init()
return m
}
// init initialises the mutex. It should be called once before the mutex is used,
// since it creates the associated resources needed for the mutex to work properly.
[inline]
pub fn (mut m Mutex) init() {
C.pthread_mutex_init(&m.mutex, C.NULL)
}
// new_rwmutex creates a new read/write mutex instance on the heap, and returns a pointer to it.
pub fn new_rwmutex() &RwMutex {
mut m := &RwMutex{}
m.init()
return m
}
// init initialises the RwMutex instance. It should be called once before the rw mutex is used,
// since it creates the associated resources needed for the mutex to work properly.
pub fn (mut m RwMutex) init() {
a := RwMutexAttr{}
C.pthread_rwlockattr_init(&a.attr)
@ -101,45 +109,100 @@ pub fn (mut m RwMutex) init() {
C.pthread_rwlock_init(&m.mutex, &a.attr)
}
// @lock(), for *manual* mutex handling, since `lock` is a keyword
// @lock locks the mutex instance (`lock` is a keyword).
// If the mutex was already locked, it will block, till it is unlocked.
[inline]
pub fn (mut m Mutex) @lock() {
C.pthread_mutex_lock(&m.mutex)
}
// unlock unlocks the mutex instance. The mutex is released, and one of
// the other threads, that were blocked, because they called @lock can continue.
[inline]
pub fn (mut m Mutex) unlock() {
C.pthread_mutex_unlock(&m.mutex)
}
// RwMutex has separate read- and write locks
// destroy frees the resources associated with the mutex instance.
// Note: the mutex itself is not freed.
[inline]
pub fn (mut m Mutex) destroy() {
res := C.pthread_mutex_destroy(&m.mutex)
if res != 0 {
cpanic(res)
}
}
// @rlock locks the given RwMutex instance for reading.
// If the mutex was already locked, it will block, and will try to get the lock,
// once the lock is released by another thread calling unlock.
// Once it succeds, it returns.
// Note: there may be several threads that are waiting for the same lock.
// Note: RwMutex has separate read and write locks.
[inline]
pub fn (mut m RwMutex) @rlock() {
C.pthread_rwlock_rdlock(&m.mutex)
}
// @lock locks the given RwMutex instance for writing.
// If the mutex was already locked, it will block, till it is unlocked,
// then it will try to get the lock, and if it can, it will return, otherwise
// it will continue waiting for the mutex to become unlocked.
// Note: there may be several threads that are waiting for the same lock.
// Note: RwMutex has separate read and write locks.
[inline]
pub fn (mut m RwMutex) @lock() {
C.pthread_rwlock_wrlock(&m.mutex)
}
// Windows SRWLocks have different function to unlock
// So provide two functions here, too, to have a common interface
// destroy frees the resources associated with the rwmutex instance.
// Note: the mutex itself is not freed.
[inline]
pub fn (mut m RwMutex) destroy() {
res := C.pthread_rwlock_destroy(&m.mutex)
if res != 0 {
cpanic(res)
}
}
// runlock unlocks the RwMutex instance, locked for reading.
// Note: Windows SRWLocks have different function to unlocking.
// To have a common portable API, there are two methods for
// unlocking here as well, even though that they do the same
// on !windows platforms.
[inline]
pub fn (mut m RwMutex) runlock() {
C.pthread_rwlock_unlock(&m.mutex)
}
// unlock unlocks the RwMutex instance, locked for writing.
// Note: Windows SRWLocks have different function to unlocking.
// To have a common portable API, there are two methods for
// unlocking here as well, even though that they do the same
// on !windows platforms.
[inline]
pub fn (mut m RwMutex) unlock() {
C.pthread_rwlock_unlock(&m.mutex)
}
// new_semaphore creates a new initialised Semaphore instance on the heap, and returns a pointer to it.
// The initial counter value of the semaphore is 0.
[inline]
pub fn new_semaphore() &Semaphore {
return new_semaphore_init(0)
}
// new_semaphore_init creates a new initialised Semaphore instance on the heap, and returns a pointer to it.
// The `n` parameter can be used to set the initial counter value of the semaphore.
pub fn new_semaphore_init(n u32) &Semaphore {
mut sem := &Semaphore{}
sem.init(n)
return sem
}
// init initialises the Semaphore instance with `n` as its initial counter value.
// It should be called once before the semaphore is used, since it creates the associated
// resources needed for the semaphore to work properly.
pub fn (mut sem Semaphore) init(n u32) {
C.atomic_store_u32(&sem.count, n)
C.pthread_mutex_init(&sem.mtx, C.NULL)
@ -150,6 +213,10 @@ pub fn (mut sem Semaphore) init(n u32) {
C.pthread_condattr_destroy(&attr.attr)
}
// post increases the counter of the semaphore by 1.
// If the resulting counter value is > 0, and if there is a thread waiting
// on the semaphore, the waiting thread will decrement the counter by 1, and
// then will continue running. See also .wait() .
pub fn (mut sem Semaphore) post() {
mut c := C.atomic_load_u32(&sem.count)
for c > 1 {
@ -157,6 +224,7 @@ pub fn (mut sem Semaphore) post() {
return
}
}
C.pthread_mutex_lock(&sem.mtx)
c = C.atomic_fetch_add_u32(&sem.count, 1)
if c == 0 {
@ -165,6 +233,11 @@ pub fn (mut sem Semaphore) post() {
C.pthread_mutex_unlock(&sem.mtx)
}
// wait will just decrement the semaphore count, if it was positive.
// It it was not positive, it will waits for the semaphore count to reach a positive number.
// When that happens, it will decrease the semaphore count, and will return.
// In effect, it allows you to block threads, until the semaphore, is posted by another thread.
// See also .post() .
pub fn (mut sem Semaphore) wait() {
mut c := C.atomic_load_u32(&sem.count)
for c > 0 {
@ -172,9 +245,9 @@ pub fn (mut sem Semaphore) wait() {
return
}
}
C.pthread_mutex_lock(&sem.mtx)
c = C.atomic_load_u32(&sem.count)
outer: for {
if c == 0 {
C.pthread_cond_wait(&sem.cond, &sem.mtx)
@ -192,6 +265,10 @@ pub fn (mut sem Semaphore) wait() {
C.pthread_mutex_unlock(&sem.mtx)
}
// try_wait tries to decrease the semaphore count by 1, if it was positive.
// If it succeeds in that, it returns true, otherwise it returns false.
// Note: try_wait should return as fast as possible so error handling is only
// done when debugging
pub fn (mut sem Semaphore) try_wait() bool {
mut c := C.atomic_load_u32(&sem.count)
for c > 0 {
@ -202,6 +279,8 @@ pub fn (mut sem Semaphore) try_wait() bool {
return false
}
// timed_wait is similar to .wait(), but it also accepts a timeout duration,
// thus it can return false early, if the timeout passed before the semaphore was posted.
pub fn (mut sem Semaphore) timed_wait(timeout time.Duration) bool {
mut c := C.atomic_load_u32(&sem.count)
for c > 0 {
@ -235,13 +314,15 @@ pub fn (mut sem Semaphore) timed_wait(timeout time.Duration) bool {
return res == 0
}
// destroy frees the resources associated with the Semaphore instance.
// Note: the semaphore instance itself is not freed.
pub fn (mut sem Semaphore) destroy() {
mut res := C.pthread_cond_destroy(&sem.cond)
if res == 0 {
res = C.pthread_mutex_destroy(&sem.mtx)
if res == 0 {
return
}
if res != 0 {
cpanic(res)
}
res = C.pthread_mutex_destroy(&sem.mtx)
if res != 0 {
cpanic(res)
}
panic(unsafe { tos_clone(&u8(C.strerror(res))) })
}

View File

@ -66,26 +66,29 @@ pub struct Semaphore {
sem C.sem_t
}
// new_mutex create and init a new mutex object. You should not call `init` again.
// new_mutex creates and initialises a new mutex instance on the heap, then returns a pointer to it.
pub fn new_mutex() &Mutex {
mut m := &Mutex{}
m.init()
return m
}
// init the mutex object.
// init initialises the mutex. It should be called once before the mutex is used,
// since it creates the associated resources needed for the mutex to work properly.
[inline]
pub fn (mut m Mutex) init() {
C.pthread_mutex_init(&m.mutex, C.NULL)
}
// new_rwmutex create and init a new rwmutex object. You should not call `init` again.
// new_rwmutex creates a new read/write mutex instance on the heap, and returns a pointer to it.
pub fn new_rwmutex() &RwMutex {
mut m := &RwMutex{}
m.init()
return m
}
// init the rwmutex object.
// init initialises the RwMutex instance. It should be called once before the rw mutex is used,
// since it creates the associated resources needed for the mutex to work properly.
pub fn (mut m RwMutex) init() {
a := RwMutexAttr{}
C.pthread_rwlockattr_init(&a.attr)
@ -96,79 +99,117 @@ pub fn (mut m RwMutex) init() {
C.pthread_rwlockattr_destroy(&a.attr) // destroy the attr when done
}
// @lock the mutex, wait and return after got the mutex lock.
// @lock locks the mutex instance (`lock` is a keyword).
// If the mutex was already locked, it will block, till it is unlocked.
[inline]
pub fn (mut m Mutex) @lock() {
C.pthread_mutex_lock(&m.mutex)
}
// unlock the mutex. The mutex is released.
// unlock unlocks the mutex instance. The mutex is released, and one of
// the other threads, that were blocked, because they called @lock can continue.
[inline]
pub fn (mut m Mutex) unlock() {
C.pthread_mutex_unlock(&m.mutex)
}
// destroy the mutex object.
// destroy frees the resources associated with the mutex instance.
// Note: the mutex itself is not freed.
pub fn (mut m Mutex) destroy() {
res := C.pthread_mutex_destroy(&m.mutex)
if res == 0 {
return
if res != 0 {
cpanic(res)
}
panic(unsafe { tos_clone(&u8(C.strerror(res))) })
}
// @rlock read-lock the rwmutex, wait and return after got read access.
// @rlock locks the given RwMutex instance for reading.
// If the mutex was already locked, it will block, and will try to get the lock,
// once the lock is released by another thread calling unlock.
// Once it succeds, it returns.
// Note: there may be several threads that are waiting for the same lock.
// Note: RwMutex has separate read and write locks.
[inline]
pub fn (mut m RwMutex) @rlock() {
C.pthread_rwlock_rdlock(&m.mutex)
}
// @lock read & write-lock the rwmutex, wait and return after got read & write access.
// @lock locks the given RwMutex instance for writing.
// If the mutex was already locked, it will block, till it is unlocked,
// then it will try to get the lock, and if it can, it will return, otherwise
// it will continue waiting for the mutex to become unlocked.
// Note: there may be several threads that are waiting for the same lock.
// Note: RwMutex has separate read and write locks.
[inline]
pub fn (mut m RwMutex) @lock() {
C.pthread_rwlock_wrlock(&m.mutex)
}
// destroy the rwmutex object.
// destroy frees the resources associated with the rwmutex instance.
// Note: the mutex itself is not freed.
pub fn (mut m RwMutex) destroy() {
res := C.pthread_rwlock_destroy(&m.mutex)
if res == 0 {
return
if res != 0 {
cpanic(res)
}
panic(unsafe { tos_clone(&u8(C.strerror(res))) })
}
// Windows SRWLocks have different function to unlock
// So provide two functions here, too, to have a common interface
// runlock unlocks the RwMutex instance, locked for reading.
// Note: Windows SRWLocks have different function to unlocking.
// To have a common portable API, there are two methods for
// unlocking here as well, even though that they do the same
// on !windows platforms.
[inline]
pub fn (mut m RwMutex) runlock() {
C.pthread_rwlock_unlock(&m.mutex)
}
// unlock the rwmutex object. The rwmutex is released.
// unlock unlocks the RwMutex instance, locked for writing.
// Note: Windows SRWLocks have different function to unlocking.
// To have a common portable API, there are two methods for
// unlocking here as well, even though that they do the same
// on !windows platforms.
[inline]
pub fn (mut m RwMutex) unlock() {
C.pthread_rwlock_unlock(&m.mutex)
}
// new_semaphore create a new semaphore, with zero initial value.
// new_semaphore creates a new initialised Semaphore instance on the heap, and returns a pointer to it.
// The initial counter value of the semaphore is 0.
[inline]
pub fn new_semaphore() &Semaphore {
return new_semaphore_init(0)
}
// new_semaphore_init create a semaphore, with `n` initial value.
// new_semaphore_init creates a new initialised Semaphore instance on the heap, and returns a pointer to it.
// The `n` parameter can be used to set the initial counter value of the semaphore.
pub fn new_semaphore_init(n u32) &Semaphore {
mut sem := &Semaphore{}
sem.init(n)
return sem
}
// init the semaphore, with `n` initial value.
// init initialises the Semaphore instance with `n` as its initial counter value.
// It should be called once before the semaphore is used, since it creates the associated
// resources needed for the semaphore to work properly.
[inline]
pub fn (mut sem Semaphore) init(n u32) {
C.sem_init(&sem.sem, 0, n)
}
// post increase the semaphore's value by 1.
// post increases/unlocks the counter of the semaphore by 1.
// If the resulting counter value is > 0, and if there is another thread waiting
// on the semaphore, the waiting thread will decrement the counter by 1
// (locking the semaphore), and then will continue running. See also .wait() .
[inline]
pub fn (mut sem Semaphore) post() {
C.sem_post(&sem.sem)
}
// wait decrease the semaphore's value by 1. If semaphore's original value is zero, then wait.
// wait will just decrement the semaphore count, if it was positive.
// It it was not positive, it will waits for the semaphore count to reach a positive number.
// When that happens, it will decrease the semaphore count (lock the semaphore), and will return.
// In effect, it allows you to block threads, until the semaphore, is posted by another thread.
// See also .post() .
pub fn (mut sem Semaphore) wait() {
for {
if C.sem_wait(&sem.sem) == 0 {
@ -180,12 +221,14 @@ pub fn (mut sem Semaphore) wait() {
continue // interrupted by signal
}
else {
panic(unsafe { tos_clone(&u8(C.strerror(C.errno))) })
cpanic_errno()
}
}
}
}
// try_wait tries to decrease the semaphore count by 1, if it was positive.
// If it succeeds in that, it returns true, otherwise it returns false.
// try_wait should return as fast as possible so error handling is only
// done when debugging
pub fn (mut sem Semaphore) try_wait() bool {
@ -199,7 +242,7 @@ pub fn (mut sem Semaphore) try_wait() bool {
return false
}
else {
panic(unsafe { tos_clone(&u8(C.strerror(C.errno))) })
cpanic_errno()
}
}
}
@ -207,8 +250,8 @@ pub fn (mut sem Semaphore) try_wait() bool {
}
}
// timed_wait decrease the semaphore's value by 1. If semaphore's original
// value is zero, then wait. If `timeout` return false.
// timed_wait is similar to .wait(), but it also accepts a timeout duration,
// thus it can return false early, if the timeout passed before the semaphore was posted.
pub fn (mut sem Semaphore) timed_wait(timeout time.Duration) bool {
$if macos {
time.sleep(timeout)
@ -230,18 +273,18 @@ pub fn (mut sem Semaphore) timed_wait(timeout time.Duration) bool {
break
}
else {
panic(unsafe { tos_clone(&u8(C.strerror(e))) })
cpanic(e)
}
}
}
return false
}
// destroy the semaphore object.
// destroy frees the resources associated with the Semaphore instance.
// Note: the semaphore instance itself is not freed.
pub fn (mut sem Semaphore) destroy() {
res := C.sem_destroy(&sem.sem)
if res == 0 {
return
if res != 0 {
cpanic(res)
}
panic(unsafe { tos_clone(&u8(C.strerror(res))) })
}

View File

@ -92,10 +92,6 @@ pub fn (mut m RwMutex) unlock() {
C.ReleaseSRWLockExclusive(&m.mx)
}
pub fn (mut m Mutex) destroy() {
// nothing to do
}
[inline]
pub fn new_semaphore() &Semaphore {
return new_semaphore_init(0)
@ -208,5 +204,14 @@ pub fn (mut sem Semaphore) timed_wait(timeout time.Duration) bool {
return res != 0
}
pub fn (s Semaphore) destroy() {
pub fn (mut m RwMutex) destroy() {
// nothing to do
}
pub fn (mut m Mutex) destroy() {
// nothing to do
}
pub fn (s Semaphore) destroy() {
// nothing to do
}

View File

@ -443,7 +443,6 @@ fn test_multi_return() {
fn test_fixed_array_of_function() {
a := [println, println]!
println(a)
assert '${a}' == '[fn (string), fn (string)]'
}
@ -452,16 +451,10 @@ struct CTypeDefStruct {
}
fn test_c_struct_typedef() {
$if macos || linux {
c := CTypeDefStruct{
mutex: sync.new_mutex()
}
assert c.str() == r'CTypeDefStruct{
mutex: &sync.Mutex{
mutex: C.pthread_mutex_t{}
}
}'
}
c := CTypeDefStruct{}
cstr := c.str()
assert cstr.starts_with('CTypeDefStruct')
assert cstr.contains('mutex: &Mutex(')
}
struct CharptrToStr {