Skip to content
This repository was archived by the owner on Oct 23, 2025. It is now read-only.

Commit 7a08507

Browse files
committed
Fix concurrent writing
Actually, it might be that all that effort was for nought: I've just realized we still have sync issues since out write calls are not for isolated chunks of stuff but often very many for one `.print` call. Thus, we need to lock the target in `.print`. We could probably do that off-thread by putting the item into a channel, but that'd require the item to implement `Send`. I'm not sure I want to do that (robustness principle and all that). I might still evaluate this in a spike branch just because I don't have enough experience to judge the effects of this. This commit surely adds a bit of tech debt, as we `Output` is now basically a `Vec<Arc<Mutex<Arc<InternalFormatter>>>`. Sorry.
1 parent 682b0ca commit 7a08507

File tree

4 files changed

+69
-29
lines changed

4 files changed

+69
-29
lines changed

src/error.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ pub enum Error {
1717
/// Error in formatting worker
1818
#[fail(display = "{}", _0)]
1919
WorkerError(String),
20+
/// Error syncing output
21+
#[fail(display = "Error syncing output")]
22+
SyncError,
2023
}
2124

2225
impl From<io::Error> for Error {

src/human.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
//! Human output
22
3-
use std::sync::Arc;
3+
use std::sync::{Arc, Mutex};
44
use termcolor::{ColorChoice, ColorSpec, StandardStream, WriteColor};
55
use {Error, Target};
66

77
/// Construct a new human output target that writes to stdout
88
pub fn stdout() -> Result<Target, Error> {
9-
Ok(Target::Human(Formatter::init_with(|| {
10-
Ok(StandardStream::stdout(ColorChoice::Auto))
11-
})?))
9+
Ok(Target::Human(Arc::new(Mutex::new(Formatter::init_with(
10+
|| Ok(StandardStream::stdout(ColorChoice::Auto)),
11+
)?))))
1212
}
1313

1414
pub use self::test_helper::{test, test_with_color};
@@ -216,6 +216,7 @@ macro_rules! render_for_humans {
216216

217217
mod test_helper {
218218
use super::Formatter;
219+
use std::sync::{Arc, Mutex};
219220
use termcolor::Buffer;
220221
use {test_buffer::TestBuffer, Target};
221222

@@ -268,7 +269,7 @@ mod test_helper {
268269
}
269270

270271
pub fn target(&self) -> Target {
271-
Target::Human(self.formatter())
272+
Target::Human(Arc::new(Mutex::new(self.formatter())))
272273
}
273274

274275
pub fn to_string(&self) -> String {

src/json.rs

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,35 @@ use serde::Serialize;
44
use serde_json::to_vec as write_json;
55
use std::io::Write;
66
use std::path::Path;
7-
use std::sync::Arc;
7+
use std::sync::{Arc, Mutex};
88
use {Error, Target};
99

1010
/// Create a new JSON output that writes to a file
1111
pub fn file<T: AsRef<Path>>(name: T) -> Result<Target, Error> {
1212
let path = name.as_ref().to_path_buf();
13-
Ok(Target::Json(Formatter::init_with(move || {
14-
use std::fs::{File, OpenOptions};
15-
use std::io::BufWriter;
16-
17-
let target = if path.exists() {
18-
let mut f = OpenOptions::new().write(true).append(true).open(&path)?;
19-
f.write_all(b"\n")?;
20-
21-
f
22-
} else {
23-
File::create(&path)?
24-
};
13+
Ok(Target::Json(Arc::new(Mutex::new(Formatter::init_with(
14+
move || {
15+
use std::fs::{File, OpenOptions};
16+
use std::io::BufWriter;
17+
18+
let target = if path.exists() {
19+
let mut f = OpenOptions::new().write(true).append(true).open(&path)?;
20+
f.write_all(b"\n")?;
21+
22+
f
23+
} else {
24+
File::create(&path)?
25+
};
2526

26-
Ok(BufWriter::new(target))
27-
})?))
27+
Ok(BufWriter::new(target))
28+
},
29+
)?))))
2830
}
2931

3032
pub use self::test_helper::test;
3133

3234
/// JSON formatter
35+
#[derive(Clone)]
3336
pub struct Formatter {
3437
inner: Arc<InternalFormatter>,
3538
}
@@ -45,9 +48,7 @@ impl Formatter {
4548

4649
/// Write a serializable item to the JSON formatter
4750
pub fn write<T: Serialize>(&self, item: &T) -> Result<(), Error> {
48-
let mut bytes = write_json(item)?;
49-
bytes.push(b'\n');
50-
self.send(Message::Write(bytes));
51+
self.send(Message::Write(write_json(item)?));
5152
Ok(())
5253
}
5354

@@ -206,6 +207,7 @@ macro_rules! render_json {
206207

207208
mod test_helper {
208209
use super::Formatter;
210+
use std::sync::{Arc, Mutex};
209211
use termcolor::Buffer;
210212
use test_buffer::TestBuffer;
211213
use Target;
@@ -262,7 +264,7 @@ mod test_helper {
262264
}
263265

264266
pub fn target(&self) -> Target {
265-
Target::Json(self.formatter())
267+
Target::Json(Arc::new(Mutex::new(self.formatter())))
266268
}
267269

268270
pub fn to_string(&self) -> String {

src/lib.rs

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ pub fn new() -> Output {
3838
}
3939

4040
/// Structure holding your output targets
41-
#[derive(Default)]
41+
#[derive(Default, Clone)]
4242
pub struct Output {
4343
targets: Vec<Target>,
4444
}
@@ -57,16 +57,19 @@ fn assert_output_is_sync_and_send() {
5757
assert_both::<Output>();
5858
}
5959

60+
use std::sync::{Arc, Mutex};
61+
6062
/// Known targets to write to
63+
#[derive(Clone)]
6164
pub enum Target {
6265
/// Human readable output
6366
///
6467
/// Will mostly be (unstructured) text, optionally with formatting.
65-
Human(human::Formatter),
68+
Human(Arc<Mutex<human::Formatter>>),
6669
/// JSON output
6770
///
6871
/// Machines like this.
69-
Json(json::Formatter),
72+
Json(Arc<Mutex<json::Formatter>>),
7073
}
7174

7275
mod error;
@@ -78,11 +81,13 @@ impl Output {
7881
for target in &mut self.targets {
7982
match target {
8083
Target::Human(fmt) => {
81-
item.render_for_humans(fmt)?;
84+
let mut fmt = fmt.lock().map_err(|_| Error::SyncError)?;
85+
item.render_for_humans(&mut *fmt)?;
8286
fmt.write("\n")?;
8387
}
8488
Target::Json(fmt) => {
85-
item.render_json(fmt)?;
89+
let mut fmt = fmt.lock().map_err(|_| Error::SyncError)?;
90+
item.render_json(&mut *fmt)?;
8691
fmt.write_separator()?;
8792
}
8893
}
@@ -96,9 +101,11 @@ impl Output {
96101
for target in &self.targets {
97102
match target {
98103
Target::Human(fmt) => {
104+
let mut fmt = fmt.lock().map_err(|_| Error::SyncError)?;
99105
fmt.flush()?;
100106
}
101107
Target::Json(fmt) => {
108+
let mut fmt = fmt.lock().map_err(|_| Error::SyncError)?;
102109
fmt.flush()?;
103110
}
104111
}
@@ -176,6 +183,33 @@ impl<'a> Render for &'a str {
176183
}
177184
}
178185

186+
/// Render a string
187+
///
188+
/// # Examples
189+
///
190+
/// ```rust
191+
/// # extern crate output;
192+
/// # use output::human;
193+
/// # fn main() -> Result<(), output::Error> {
194+
/// # let test_target = human::test();
195+
/// let mut out = output::new().add_target(test_target.target());
196+
/// out.print(String::from("Hello, World!"))?;
197+
/// # out.flush()?;
198+
/// # assert_eq!(test_target.to_string(), "Hello, World!\n");
199+
/// # Ok(()) }
200+
/// ```
201+
impl<'a> Render for String {
202+
fn render_for_humans(&self, fmt: &mut human::Formatter) -> Result<(), Error> {
203+
fmt.write(self.as_bytes())?;
204+
Ok(())
205+
}
206+
207+
fn render_json(&self, fmt: &mut json::Formatter) -> Result<(), Error> {
208+
fmt.write(&self)?;
209+
Ok(())
210+
}
211+
}
212+
179213
pub mod components;
180214
pub mod human;
181215
pub mod json;

0 commit comments

Comments
 (0)