mirror of
https://github.com/vlang/v.git
synced 2025-08-05 18:57:45 -04:00
log: improve the most common use case (#19242)
This commit is contained in:
parent
55575fd7bd
commit
6fb4a481f8
@ -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
|
||||
|
@ -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
69
vlib/log/common.v
Normal 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
42
vlib/log/default.c.v
Normal 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
55
vlib/log/default.v
Normal 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
37
vlib/log/default_test.v
Normal 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()
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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')
|
||||
}
|
||||
|
15
vlib/log/logger_interface.v
Normal file
15
vlib/log/logger_interface.v
Normal 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
79
vlib/log/safe_log.v
Normal 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
11
vlib/sync/common_mutex.v
Normal 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
11
vlib/sync/sync.c.v
Normal 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)
|
||||
}
|
@ -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))) })
|
||||
}
|
||||
|
@ -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))) })
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user