I'm using slog in combination with tokio and futures-await.
I basically have an instance of slog::Logger without generic arguments so the default std::sync::Arc<dyn slog::SendSyncRefUnwindSafeDrain<Err=slog::private::NeverStruct, Ok=()>> is used.
I'm trying to send that (root) logger instance to a worker thread through tokio::spawn, but the compiler complains with the following message:
"dyn slog::SendSyncRefUnwindSafeDrain<Err=slog::private::NeverStruct, Ok=()> cannot be sent between threads safely"
The point i don't actually understand is that the trait object has Send + Sync as super-traits, shouldn't this indicate to the compiler that the trait object is Send + Sync?
The error traces are quite large because this occurs within an async context, so i tried to put 'where' bounds on my structures which contain the logger: where slog::Logger: Send + Sync. These bounds don't cause issues so i'm out of ideas.
I suppose i'm misunderstanding something, but someone needs to explicitly tell me what. I hope someone has encountered this before and can tell me what's missing.
I'll be building a small example to post here in a bit.
I think the issue happens when the compiler automatically uses a borrow of the logger instance, instead of moving.
The order of compiler checks made it difficult to track this down;
It first checks required impls, then the borrow checker reports in. The other way around would have provided more helpful error messages in this case.
Hey, @Bert-Proesmans, I'm having this exact issue now but don't completely understand your last comment. If you still remember (its clearly been a while), would you pointing me in the right direction?
Looking at my comment it seems I needed to add the 'move' keyword to the closure that goes into the Tokio runtime.
If I remember correctly my situation contained chained futures and each closure used a cloned arc-instance of the same slog root. So i already had one owned instance per closure ready but didn't annotate the closures itself.
I also remember reading a blog post (by Aturon?) around the same time. It discusses an explicit move syntax;
{ [variable] }
I'm unable to find this post at the moment and unsure it is actually relevant here. It's mentioned just to be complete.
let (mut writer, reader) = framed.split();
// Variable name shadowed, but doesn't matter in this case.
// Copy is moved into for_each closure (r45)
let log = log.clone();
// Extra copy to move into error closure (r53)
let log_err = log.clone();
[..]
.or_else(move |err| {
let msg = format!("TCP Server - Socket closed with error: {:?})", err);
println!("{}", msg);
// Use log_err here
error!(log_err, "{}", msg; "daemon-name" => PROGRAM_NAME);
Err(err)
})
The reason is because you use closures for each continuation. The compiler cannot deduce in this code style that your use of the log variable is safe. The clone makes an owned value that is safe to move into the closures, which satisfies the compiler constraints.
It's one of the biggest usability issues that disappear when using the async/await syntax, because the compiler can deduce that the usage of variable log is safe across await/continuation points.