diff --git a/examples/log.v b/examples/log.v index e6426603ed..d2635577f6 100644 --- a/examples/log.v +++ b/examples/log.v @@ -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 diff --git a/vlib/log/README.md b/vlib/log/README.md index 2c278b14d0..fdb643c47f 100644 --- a/vlib/log/README.md +++ b/vlib/log/README.md @@ -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] +} +``` diff --git a/vlib/log/common.v b/vlib/log/common.v new file mode 100644 index 0000000000..6349affb9c --- /dev/null +++ b/vlib/log/common.v @@ -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 } + } +} diff --git a/vlib/log/default.c.v b/vlib/log/default.c.v new file mode 100644 index 0000000000..d265bdd93c --- /dev/null +++ b/vlib/log/default.c.v @@ -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) + } +} diff --git a/vlib/log/default.v b/vlib/log/default.v new file mode 100644 index 0000000000..ee25d86d9f --- /dev/null +++ b/vlib/log/default.v @@ -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) +} diff --git a/vlib/log/default_test.v b/vlib/log/default_test.v new file mode 100644 index 0000000000..d6b1076fb0 --- /dev/null +++ b/vlib/log/default_test.v @@ -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() +} diff --git a/vlib/log/log.v b/vlib/log/log.v index 8ff87f2d88..179617777c 100644 --- a/vlib/log/log.v +++ b/vlib/log/log.v @@ -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() + } +} diff --git a/vlib/log/log_test.v b/vlib/log/log_test.v index e6c1a7337b..576ed9ffb4 100644 --- a/vlib/log/log_test.v +++ b/vlib/log/log_test.v @@ -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') } diff --git a/vlib/log/logger_interface.v b/vlib/log/logger_interface.v new file mode 100644 index 0000000000..d716a78ac2 --- /dev/null +++ b/vlib/log/logger_interface.v @@ -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() +} diff --git a/vlib/log/safe_log.v b/vlib/log/safe_log.v new file mode 100644 index 0000000000..8aaddf113d --- /dev/null +++ b/vlib/log/safe_log.v @@ -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) +} diff --git a/vlib/sync/common_mutex.v b/vlib/sync/common_mutex.v new file mode 100644 index 0000000000..f2cf330ed7 --- /dev/null +++ b/vlib/sync/common_mutex.v @@ -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)})' +} diff --git a/vlib/sync/sync.c.v b/vlib/sync/sync.c.v new file mode 100644 index 0000000000..99de1653c7 --- /dev/null +++ b/vlib/sync/sync.c.v @@ -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) +} diff --git a/vlib/sync/sync_darwin.c.v b/vlib/sync/sync_darwin.c.v index 8d58230de0..5fb497ca4f 100644 --- a/vlib/sync/sync_darwin.c.v +++ b/vlib/sync/sync_darwin.c.v @@ -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))) }) } diff --git a/vlib/sync/sync_default.c.v b/vlib/sync/sync_default.c.v index be15deada9..43dde304df 100644 --- a/vlib/sync/sync_default.c.v +++ b/vlib/sync/sync_default.c.v @@ -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))) }) } diff --git a/vlib/sync/sync_windows.c.v b/vlib/sync/sync_windows.c.v index 6ec84fbbc2..a93145db35 100644 --- a/vlib/sync/sync_windows.c.v +++ b/vlib/sync/sync_windows.c.v @@ -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 } diff --git a/vlib/v/tests/str_gen_test.v b/vlib/v/tests/str_gen_test.v index 9730d1f1c3..6b0150e424 100644 --- a/vlib/v/tests/str_gen_test.v +++ b/vlib/v/tests/str_gen_test.v @@ -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 {