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 import log
fn main() { 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{} mut l := log.Log{}
l.set_level(.info) l.set_level(.info)
// Make a new file called info.log in the current folder // Make a new file called info.log in the current folder

View File

@ -3,3 +3,48 @@
`log` provides your application logging services. `log` provides your application logging services.
You can log to file or to the console and use different You can log to file or to the console and use different
logging levels, so that you are not overwhelmed by the logs. 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 os
import time 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 // Log represents a logging object
pub struct Log { pub struct Log {
@ -94,16 +18,20 @@ pub mut:
} }
// get_level gets the internal logging level. // 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 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) { pub fn (mut l Log) set_level(level Level) {
l.level = level l.level = level
} }
// set_output_level sets the internal logging output to `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) { pub fn (mut l Log) set_output_level(level Level) {
l.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. // log_file writes log line `s` with `level` to the log file.
fn (mut l Log) log_file(s string, level Level) { 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) e := tag_to_file(level)
l.ofile.writeln('${timestamp} [${e}] ${s}') or { panic(err) } l.ofile.writeln('${timestamp} [${e}] ${s}') or { panic(err) }
} }
// log_cli writes log line `s` with `level` to stdout. // log_cli writes log line `s` with `level` to stdout.
fn (l &Log) log_cli(s string, level Level) { 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) e := tag_to_cli(level)
println('${timestamp} [${e}] ${s}') println('${timestamp} [${e}] ${s}')
} }
@ -219,3 +147,13 @@ pub fn (mut l Log) debug(s string) {
} }
l.send_output(s, .debug) 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 module log
import time
fn log_mutable_statements(mut log Log) { fn log_mutable_statements(mut log Log) {
println(@FN + ' start') println(@FN + ' start')
log.info('info') log.info('info')
@ -30,10 +28,6 @@ fn logger_mutable_statements(mut log Logger) {
println(@FN + ' end') println(@FN + ' end')
} }
fn delay() {
time.sleep(1 * time.second)
}
// Note that Log and Logger methods requires a mutable instance // Note that Log and Logger methods requires a mutable instance
// new_log create and return a new Log reference // new_log create and return a new Log reference
@ -63,8 +57,8 @@ fn test_log_mutable_reference() {
println(@FN + ' start') println(@FN + ' start')
mut log := new_log() mut log := new_log()
assert typeof(log).name == '&log.Log' assert typeof(log).name == '&log.Log'
spawn log_mutable_statements(mut log) t := spawn log_mutable_statements(mut log)
delay() // wait to finish t.wait()
assert true assert true
println(@FN + ' end') println(@FN + ' end')
} }
@ -75,8 +69,8 @@ fn test_logger_mutable_reference() {
mut logger := new_log_as_logger() mut logger := new_log_as_logger()
logger.set_level(.warn) logger.set_level(.warn)
assert typeof(logger).name == '&log.Logger' assert typeof(logger).name == '&log.Logger'
spawn logger_mutable_statements(mut logger) t := spawn logger_mutable_statements(mut logger)
delay() // wait to finish t.wait()
assert true assert true
println(@FN + ' end') 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_rdlock(voidptr) int
fn C.pthread_rwlock_wrlock(voidptr) int fn C.pthread_rwlock_wrlock(voidptr) int
fn C.pthread_rwlock_unlock(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_init(voidptr) int
fn C.pthread_condattr_setpshared(voidptr, int) int fn C.pthread_condattr_setpshared(voidptr, int) int
fn C.pthread_condattr_destroy(voidptr) int fn C.pthread_condattr_destroy(voidptr) int
@ -76,22 +77,29 @@ mut:
count u32 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 { pub fn new_mutex() &Mutex {
mut m := &Mutex{} mut m := &Mutex{}
m.init() m.init()
return m 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() { pub fn (mut m Mutex) init() {
C.pthread_mutex_init(&m.mutex, C.NULL) 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 { pub fn new_rwmutex() &RwMutex {
mut m := &RwMutex{} mut m := &RwMutex{}
m.init() m.init()
return m 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() { pub fn (mut m RwMutex) init() {
a := RwMutexAttr{} a := RwMutexAttr{}
C.pthread_rwlockattr_init(&a.attr) C.pthread_rwlockattr_init(&a.attr)
@ -101,45 +109,100 @@ pub fn (mut m RwMutex) init() {
C.pthread_rwlock_init(&m.mutex, &a.attr) 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() { pub fn (mut m Mutex) @lock() {
C.pthread_mutex_lock(&m.mutex) 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() { pub fn (mut m Mutex) unlock() {
C.pthread_mutex_unlock(&m.mutex) 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() { pub fn (mut m RwMutex) @rlock() {
C.pthread_rwlock_rdlock(&m.mutex) 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() { pub fn (mut m RwMutex) @lock() {
C.pthread_rwlock_wrlock(&m.mutex) C.pthread_rwlock_wrlock(&m.mutex)
} }
// Windows SRWLocks have different function to unlock // destroy frees the resources associated with the rwmutex instance.
// So provide two functions here, too, to have a common interface // 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() { pub fn (mut m RwMutex) runlock() {
C.pthread_rwlock_unlock(&m.mutex) 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() { pub fn (mut m RwMutex) unlock() {
C.pthread_rwlock_unlock(&m.mutex) 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] [inline]
pub fn new_semaphore() &Semaphore { pub fn new_semaphore() &Semaphore {
return new_semaphore_init(0) 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 { pub fn new_semaphore_init(n u32) &Semaphore {
mut sem := &Semaphore{} mut sem := &Semaphore{}
sem.init(n) sem.init(n)
return sem 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) { pub fn (mut sem Semaphore) init(n u32) {
C.atomic_store_u32(&sem.count, n) C.atomic_store_u32(&sem.count, n)
C.pthread_mutex_init(&sem.mtx, C.NULL) 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) 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() { pub fn (mut sem Semaphore) post() {
mut c := C.atomic_load_u32(&sem.count) mut c := C.atomic_load_u32(&sem.count)
for c > 1 { for c > 1 {
@ -157,6 +224,7 @@ pub fn (mut sem Semaphore) post() {
return return
} }
} }
C.pthread_mutex_lock(&sem.mtx) C.pthread_mutex_lock(&sem.mtx)
c = C.atomic_fetch_add_u32(&sem.count, 1) c = C.atomic_fetch_add_u32(&sem.count, 1)
if c == 0 { if c == 0 {
@ -165,6 +233,11 @@ pub fn (mut sem Semaphore) post() {
C.pthread_mutex_unlock(&sem.mtx) 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() { pub fn (mut sem Semaphore) wait() {
mut c := C.atomic_load_u32(&sem.count) mut c := C.atomic_load_u32(&sem.count)
for c > 0 { for c > 0 {
@ -172,9 +245,9 @@ pub fn (mut sem Semaphore) wait() {
return return
} }
} }
C.pthread_mutex_lock(&sem.mtx) C.pthread_mutex_lock(&sem.mtx)
c = C.atomic_load_u32(&sem.count) c = C.atomic_load_u32(&sem.count)
outer: for { outer: for {
if c == 0 { if c == 0 {
C.pthread_cond_wait(&sem.cond, &sem.mtx) C.pthread_cond_wait(&sem.cond, &sem.mtx)
@ -192,6 +265,10 @@ pub fn (mut sem Semaphore) wait() {
C.pthread_mutex_unlock(&sem.mtx) 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 { pub fn (mut sem Semaphore) try_wait() bool {
mut c := C.atomic_load_u32(&sem.count) mut c := C.atomic_load_u32(&sem.count)
for c > 0 { for c > 0 {
@ -202,6 +279,8 @@ pub fn (mut sem Semaphore) try_wait() bool {
return false 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 { pub fn (mut sem Semaphore) timed_wait(timeout time.Duration) bool {
mut c := C.atomic_load_u32(&sem.count) mut c := C.atomic_load_u32(&sem.count)
for c > 0 { for c > 0 {
@ -235,13 +314,15 @@ pub fn (mut sem Semaphore) timed_wait(timeout time.Duration) bool {
return res == 0 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() { pub fn (mut sem Semaphore) destroy() {
mut res := C.pthread_cond_destroy(&sem.cond) mut res := C.pthread_cond_destroy(&sem.cond)
if res == 0 { if res != 0 {
res = C.pthread_mutex_destroy(&sem.mtx) cpanic(res)
if res == 0 { }
return 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 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 { pub fn new_mutex() &Mutex {
mut m := &Mutex{} mut m := &Mutex{}
m.init() m.init()
return m 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() { pub fn (mut m Mutex) init() {
C.pthread_mutex_init(&m.mutex, C.NULL) 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 { pub fn new_rwmutex() &RwMutex {
mut m := &RwMutex{} mut m := &RwMutex{}
m.init() m.init()
return m 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() { pub fn (mut m RwMutex) init() {
a := RwMutexAttr{} a := RwMutexAttr{}
C.pthread_rwlockattr_init(&a.attr) 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 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() { pub fn (mut m Mutex) @lock() {
C.pthread_mutex_lock(&m.mutex) 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() { pub fn (mut m Mutex) unlock() {
C.pthread_mutex_unlock(&m.mutex) 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() { pub fn (mut m Mutex) destroy() {
res := C.pthread_mutex_destroy(&m.mutex) res := C.pthread_mutex_destroy(&m.mutex)
if res == 0 { if res != 0 {
return 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() { pub fn (mut m RwMutex) @rlock() {
C.pthread_rwlock_rdlock(&m.mutex) 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() { pub fn (mut m RwMutex) @lock() {
C.pthread_rwlock_wrlock(&m.mutex) 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() { pub fn (mut m RwMutex) destroy() {
res := C.pthread_rwlock_destroy(&m.mutex) res := C.pthread_rwlock_destroy(&m.mutex)
if res == 0 { if res != 0 {
return cpanic(res)
} }
panic(unsafe { tos_clone(&u8(C.strerror(res))) })
} }
// Windows SRWLocks have different function to unlock // runlock unlocks the RwMutex instance, locked for reading.
// So provide two functions here, too, to have a common interface // 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() { pub fn (mut m RwMutex) runlock() {
C.pthread_rwlock_unlock(&m.mutex) 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() { pub fn (mut m RwMutex) unlock() {
C.pthread_rwlock_unlock(&m.mutex) 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] [inline]
pub fn new_semaphore() &Semaphore { pub fn new_semaphore() &Semaphore {
return new_semaphore_init(0) 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 { pub fn new_semaphore_init(n u32) &Semaphore {
mut sem := &Semaphore{} mut sem := &Semaphore{}
sem.init(n) sem.init(n)
return sem 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) { pub fn (mut sem Semaphore) init(n u32) {
C.sem_init(&sem.sem, 0, n) 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() { pub fn (mut sem Semaphore) post() {
C.sem_post(&sem.sem) 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() { pub fn (mut sem Semaphore) wait() {
for { for {
if C.sem_wait(&sem.sem) == 0 { if C.sem_wait(&sem.sem) == 0 {
@ -180,12 +221,14 @@ pub fn (mut sem Semaphore) wait() {
continue // interrupted by signal continue // interrupted by signal
} }
else { 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 // try_wait should return as fast as possible so error handling is only
// done when debugging // done when debugging
pub fn (mut sem Semaphore) try_wait() bool { pub fn (mut sem Semaphore) try_wait() bool {
@ -199,7 +242,7 @@ pub fn (mut sem Semaphore) try_wait() bool {
return false return false
} }
else { 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 // timed_wait is similar to .wait(), but it also accepts a timeout duration,
// value is zero, then wait. If `timeout` return false. // 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 { pub fn (mut sem Semaphore) timed_wait(timeout time.Duration) bool {
$if macos { $if macos {
time.sleep(timeout) time.sleep(timeout)
@ -230,18 +273,18 @@ pub fn (mut sem Semaphore) timed_wait(timeout time.Duration) bool {
break break
} }
else { else {
panic(unsafe { tos_clone(&u8(C.strerror(e))) }) cpanic(e)
} }
} }
} }
return false 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() { pub fn (mut sem Semaphore) destroy() {
res := C.sem_destroy(&sem.sem) res := C.sem_destroy(&sem.sem)
if res == 0 { if res != 0 {
return 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) C.ReleaseSRWLockExclusive(&m.mx)
} }
pub fn (mut m Mutex) destroy() {
// nothing to do
}
[inline] [inline]
pub fn new_semaphore() &Semaphore { pub fn new_semaphore() &Semaphore {
return new_semaphore_init(0) return new_semaphore_init(0)
@ -208,5 +204,14 @@ pub fn (mut sem Semaphore) timed_wait(timeout time.Duration) bool {
return res != 0 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() { fn test_fixed_array_of_function() {
a := [println, println]! a := [println, println]!
println(a)
assert '${a}' == '[fn (string), fn (string)]' assert '${a}' == '[fn (string), fn (string)]'
} }
@ -452,16 +451,10 @@ struct CTypeDefStruct {
} }
fn test_c_struct_typedef() { fn test_c_struct_typedef() {
$if macos || linux { c := CTypeDefStruct{}
c := CTypeDefStruct{ cstr := c.str()
mutex: sync.new_mutex() assert cstr.starts_with('CTypeDefStruct')
} assert cstr.contains('mutex: &Mutex(')
assert c.str() == r'CTypeDefStruct{
mutex: &sync.Mutex{
mutex: C.pthread_mutex_t{}
}
}'
}
} }
struct CharptrToStr { struct CharptrToStr {