console-rs / indicatif

A command line progress reporting library for Rust
MIT License
4.43k stars 243 forks source link

0.17.0-rc.2 panic on win10 git-bash #358

Closed ubearline closed 2 years ago

ubearline commented 2 years ago

demo

extern crate indicatif;

use indicatif::{ProgressBar, ProgressStyle, MultiProgress};
use std::thread::sleep;
use std::time::Duration;

fn main() {
    let m = MultiProgress::new();
    let pb = m.insert(0, ProgressBar::new(128));
    let filename= "some_file";
    pb.set_style(
        ProgressStyle::default_bar()
            .template(
                ("{spinner:.green} [{elapsed_precise}] [{bar:50.green/cyan}]{percent:>3}% ["
                    .to_string()
                    + filename
                    + "] [{pos}/{len}]{eta} {msg}")
                    .as_str(),
            )
            .with_key("eta", |state| {
                let eta = state.eta().as_secs_f64();
                if eta > 0.0 {
                    format!(" [{:.1}s]", state.eta().as_secs_f64())
                } else {
                    "".to_string()
                }
            })
            .progress_chars(">>-"),
    );

    for i in 0..128{
        pb.set_position(i);
        sleep(Duration::from_millis(10))
    }
    pb.finish_with_message("done");
}

win10 powershell normal

图片

win10 cmd normal

图片

win10 git-bush panic (It was working in the last version 0.17.0-beta.1)

图片

panic log

thread 'main' panicked at 'attempt to subtract with overflow', C:\Users\Dami\.cargo\registry\src\mirrors.sjtug.sjtu.edu.cn-7a04d2510079875b\indicatif-0.17.0-rc.2\src\draw_target.rs:402:44
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: PoisonError { .. }', C:\Users\Dami\.cargo\registry\src\mirrors.sjtug.sjtu.edu.cn-7a04d2510079875b\indicatif-0.17.0-rc.2\src\draw_target.rs:165:22
stack backtrace:
   0:     0x7ff66e9af4e0 - std::backtrace_rs::backtrace::dbghelp::trace
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\/library\std\src\..\..\backtrace\src\backtrace\dbghelp.rs:98
   1:     0x7ff66e9af4e0 - std::backtrace_rs::backtrace::trace_unsynchronized
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\/library\std\src\..\..\backtrace\src\backtrace\mod.rs:66
   2:     0x7ff66e9af4e0 - std::sys_common::backtrace::_print_fmt
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\/library\std\src\sys_common\backtrace.rs:67
   3:     0x7ff66e9af4e0 - std::sys_common::backtrace::_print::impl$0::fmt
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\/library\std\src\sys_common\backtrace.rs:46
   4:     0x7ff66e9c4bba - core::fmt::write
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\/library\core\src\fmt\mod.rs:1168
   5:     0x7ff66e9ac638 - std::io::Write::write_fmt<std::sys::windows::stdio::Stderr>
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\/library\std\src\io\mod.rs:1653
   6:     0x7ff66e9b182b - std::sys_common::backtrace::_print
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\/library\std\src\sys_common\backtrace.rs:49
   7:     0x7ff66e9b182b - std::sys_common::backtrace::print
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\/library\std\src\sys_common\backtrace.rs:36
   8:     0x7ff66e9b182b - std::panicking::default_hook::closure$1
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\/library\std\src\panicking.rs:290
   9:     0x7ff66e9b1324 - std::panicking::default_hook
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\/library\std\src\panicking.rs:307
  10:     0x7ff66e9b1d57 - std::panicking::rust_panic_with_hook
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\/library\std\src\panicking.rs:691
  11:     0x7ff66e9b1c0d - std::panicking::begin_panic_handler::closure$0
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\/library\std\src\panicking.rs:581
  12:     0x7ff66e9afe37 - std::sys_common::backtrace::__rust_end_short_backtrace<std::panicking::begin_panic_handler::closure$0,never$>
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\/library\std\src\sys_common\backtrace.rs:139
  13:     0x7ff66e9b18e9 - std::panicking::begin_panic_handler
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\/library\std\src\panicking.rs:577
  14:     0x7ff66e9cb125 - core::panicking::panic_fmt
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\/library\core\src\panicking.rs:135
  15:     0x7ff66e9cb233 - core::result::unwrap_failed
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\/library\core\src\result.rs:1737
  16:     0x7ff66e8b81b9 - enum$<core::result::Result<std::sync::rwlock::RwLockWriteGuard<indicatif::draw_target::MultiProgressState>,std::sync::poison::PoisonError<std::sync::rwlock::RwLockWriteGuard<indicatif::draw_target::MultiProgressState> > > >::unwrap<std::sync::rwlock::RwLo
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\library\core\src\result.rs:1065
  17:     0x7ff66e8ca344 - indicatif::draw_target::ProgressDrawTarget::apply_draw_state
                               at C:\Users\Dami\.cargo\registry\src\mirrors.sjtug.sjtu.edu.cn-7a04d2510079875b\indicatif-0.17.0-rc.2\src\draw_target.rs:163
  18:     0x7ff66e8ba075 - indicatif::state::ProgressState::draw
                               at C:\Users\Dami\.cargo\registry\src\mirrors.sjtug.sjtu.edu.cn-7a04d2510079875b\indicatif-0.17.0-rc.2\src\state.rs:245
  19:     0x7ff66e8b929f - indicatif::state::ProgressState::update_and_force_draw<indicatif::state::impl$0::finish_and_clear::closure$0>
                               at C:\Users\Dami\.cargo\registry\src\mirrors.sjtug.sjtu.edu.cn-7a04d2510079875b\indicatif-0.17.0-rc.2\src\state.rs:138
  20:     0x7ff66e8b9e0e - indicatif::state::ProgressState::finish_and_clear
                               at C:\Users\Dami\.cargo\registry\src\mirrors.sjtug.sjtu.edu.cn-7a04d2510079875b\indicatif-0.17.0-rc.2\src\state.rs:188
  21:     0x7ff66e8b9f0f - indicatif::state::ProgressState::finish_using_style
                               at C:\Users\Dami\.cargo\registry\src\mirrors.sjtug.sjtu.edu.cn-7a04d2510079875b\indicatif-0.17.0-rc.2\src\state.rs:221
  22:     0x7ff66e8ba0fb - indicatif::state::impl$1::drop
                               at C:\Users\Dami\.cargo\registry\src\mirrors.sjtug.sjtu.edu.cn-7a04d2510079875b\indicatif-0.17.0-rc.2\src\state.rs:256
  23:     0x7ff66e8b388f - core::ptr::drop_in_place<indicatif::state::ProgressState>
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\library\core\src\ptr\mod.rs:188
  24:     0x7ff66e8b433e - core::ptr::drop_in_place<core::cell::UnsafeCell<indicatif::state::ProgressState> >
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\library\core\src\ptr\mod.rs:188
  25:     0x7ff66e8b437e - core::ptr::drop_in_place<std::sync::mutex::Mutex<indicatif::state::ProgressState> >
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\library\core\src\ptr\mod.rs:188
  26:     0x7ff66e8b27b2 - alloc::sync::Arc<std::sync::mutex::Mutex<indicatif::state::ProgressState> >::drop_slow<std::sync::mutex::Mutex<indicatif::state::ProgressState> >
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\library\alloc\src\sync.rs:1092
  27:     0x7ff66e8b29a2 - alloc::sync::impl$27::drop<std::sync::mutex::Mutex<indicatif::state::ProgressState> >
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\library\alloc\src\sync.rs:1653
  28:     0x7ff66e8b330e - core::ptr::drop_in_place<alloc::sync::Arc<std::sync::mutex::Mutex<indicatif::state::ProgressState> > >
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\library\core\src\ptr\mod.rs:188
  29:     0x7ff66e8b3c5e - core::ptr::drop_in_place<indicatif::progress_bar::ProgressBar>
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\library\core\src\ptr\mod.rs:188
  30:     0x7ff66e8b1d4a - bug::main
                               at E:\project\bug\src\main.rs:37
  31:     0x7ffbb8ef1030 - <unknown>
  32:     0x7ffbb8ef4cb2 - is_exception_typeof
  33:     0x7ffbb8eff0e4 - _C_specific_handler
  34:     0x7ffbb8ef3f28 - is_exception_typeof
  35:     0x7ffbb8effa21 - _CxxFrameHandler3
  36:     0x7ffbd7ff214f - _chkstk
  37:     0x7ffbd7f80939 - RtlUnwindEx
  38:     0x7ffbb8eff5ae - _C_specific_handler
  39:     0x7ffbb8ef2bf5 - is_exception_typeof
  40:     0x7ffbb8ef300d - is_exception_typeof
  41:     0x7ffbb8ef4024 - is_exception_typeof
  42:     0x7ffbb8effa21 - _CxxFrameHandler3
  43:     0x7ffbd7ff20cf - _chkstk
  44:     0x7ffbd7fa1454 - RtlRaiseException
  45:     0x7ffbd7fa11a5 - RtlRaiseException
  46:     0x7ffbd58c4f69 - RaiseException
  47:     0x7ffbb8ef6480 - CxxThrowException
  48:     0x7ff66e9b6a5e - panic_unwind::real_imp::panic
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\/library\panic_unwind\src\seh.rs:313
  49:     0x7ff66e9b6a5e - panic_unwind::__rust_start_panic
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\/library\panic_unwind\src\lib.rs:108
  50:     0x7ff66e9b20cb - std::panicking::rust_panic
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\/library\std\src\panicking.rs:739
  51:     0x7ff66e9b1eef - std::panicking::rust_panic_with_hook
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\/library\std\src\panicking.rs:709
  52:     0x7ff66e9b1bd2 - std::panicking::begin_panic_handler::closure$0
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\/library\std\src\panicking.rs:579
  53:     0x7ff66e9afe37 - std::sys_common::backtrace::__rust_end_short_backtrace<std::panicking::begin_panic_handler::closure$0,never$>
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\/library\std\src\sys_common\backtrace.rs:139
  54:     0x7ff66e9b18e9 - std::panicking::begin_panic_handler
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\/library\std\src\panicking.rs:577
  55:     0x7ff66e9cb125 - core::panicking::panic_fmt
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\/library\core\src\panicking.rs:135
  56:     0x7ff66e9caffc - core::panicking::panic
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\/library\core\src\panicking.rs:48
  57:     0x7ff66e8cbcf8 - indicatif::draw_target::ProgressDrawState::draw_to_term
                               at C:\Users\Dami\.cargo\registry\src\mirrors.sjtug.sjtu.edu.cn-7a04d2510079875b\indicatif-0.17.0-rc.2\src\draw_target.rs:402
  58:     0x7ff66e8caaa8 - indicatif::draw_target::ProgressDrawTarget::apply_draw_state
                               at C:\Users\Dami\.cargo\registry\src\mirrors.sjtug.sjtu.edu.cn-7a04d2510079875b\indicatif-0.17.0-rc.2\src\draw_target.rs:189
  59:     0x7ff66e8cb5d7 - indicatif::draw_target::MultiProgressState::draw
                               at C:\Users\Dami\.cargo\registry\src\mirrors.sjtug.sjtu.edu.cn-7a04d2510079875b\indicatif-0.17.0-rc.2\src\draw_target.rs:303
  60:     0x7ff66e8ca3ca - indicatif::draw_target::ProgressDrawTarget::apply_draw_state
                               at C:\Users\Dami\.cargo\registry\src\mirrors.sjtug.sjtu.edu.cn-7a04d2510079875b\indicatif-0.17.0-rc.2\src\draw_target.rs:163
  61:     0x7ff66e8ba075 - indicatif::state::ProgressState::draw
                               at C:\Users\Dami\.cargo\registry\src\mirrors.sjtug.sjtu.edu.cn-7a04d2510079875b\indicatif-0.17.0-rc.2\src\state.rs:245
  62:     0x7ff66e8b9189 - indicatif::state::ProgressState::update_and_draw<indicatif::progress_bar::impl$1::set_position::closure$0>
                               at C:\Users\Dami\.cargo\registry\src\mirrors.sjtug.sjtu.edu.cn-7a04d2510079875b\indicatif-0.17.0-rc.2\src\state.rs:127
  63:     0x7ff66e8bba8d - indicatif::progress_bar::ProgressBar::update_and_draw<indicatif::progress_bar::impl$1::set_position::closure$0>
                               at C:\Users\Dami\.cargo\registry\src\mirrors.sjtug.sjtu.edu.cn-7a04d2510079875b\indicatif-0.17.0-rc.2\src\progress_bar.rs:542
  64:     0x7ff66e8bb712 - indicatif::progress_bar::ProgressBar::set_position
                               at C:\Users\Dami\.cargo\registry\src\mirrors.sjtug.sjtu.edu.cn-7a04d2510079875b\indicatif-0.17.0-rc.2\src\progress_bar.rs:264
  65:     0x7ff66e8b1cc9 - bug::main
                               at E:\project\bug\src\main.rs:33
  66:     0x7ff66e8b179b - core::ops::function::FnOnce::call_once<void (*)(),tuple$<> >
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\library\core\src\ops\function.rs:227
  67:     0x7ff66e8b1a4b - std::sys_common::backtrace::__rust_begin_short_backtrace<void (*)(),tuple$<> >
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\library\std\src\sys_common\backtrace.rs:123
  68:     0x7ff66e8b1211 - std::rt::lang_start::closure$0<tuple$<> >
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\library\std\src\rt.rs:145
  69:     0x7ff66e9aed9f - core::ops::function::impls::impl$2::call_once
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\library\core\src\ops\function.rs:259
  70:     0x7ff66e9aed9f - std::panicking::try::do_call
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\/library\std\src\panicking.rs:485
  71:     0x7ff66e9aed9f - std::panicking::try
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\/library\std\src\panicking.rs:449
  72:     0x7ff66e9aed9f - std::panic::catch_unwind
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\/library\std\src\panic.rs:136
  73:     0x7ff66e9aed9f - std::rt::lang_start_internal::closure$2
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\/library\std\src\rt.rs:128
  74:     0x7ff66e9aed9f - std::panicking::try::do_call
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\/library\std\src\panicking.rs:485
  75:     0x7ff66e9aed9f - std::panicking::try
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\/library\std\src\panicking.rs:449
  76:     0x7ff66e9aed9f - std::panic::catch_unwind
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\/library\std\src\panic.rs:136
  77:     0x7ff66e9aed9f - std::rt::lang_start_internal
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\/library\std\src\rt.rs:128
  78:     0x7ff66e8b11df - std::rt::lang_start<tuple$<> >
                               at /rustc/8cdb3cd94efece1e17cbd8f6edb1dc1a482779a0\library\std\src\rt.rs:144
  79:     0x7ff66e8b1e16 - main
  80:     0x7ff66e9c985c - invoke_main
                               at d:\agent\_work\2\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:78
  81:     0x7ff66e9c985c - __scrt_common_main_seh
                               at d:\agent\_work\2\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288
  82:     0x7ffbd5fa7034 - BaseThreadInitThunk
  83:     0x7ffbd7fa2651 - RtlUserThreadStart
thread panicked while panicking. aborting.
djc commented 2 years ago

@sigmaSd / @chris-laplante any suggestions for what might be wrong here?

thread 'main' panicked at 'attempt to subtract with overflow', C:\Users\Dami\.cargo\registry\src\mirrors.sjtug.sjtu.edu.cn-7a04d2510079875b\indicatif-0.17.0-rc.2\src\draw_target.rs:402:44

That's this code:

399                // Keep the cursor on the right terminal side
400                // So that next user writes/prints will happen on the next line
401                let line_width = console::measure_text_width(line);
402                term.write_str(&" ".repeat(usize::from(term.size().1) - line_width))?;

I guess it's definitely possible that the line is longer than the console is wide, so we should account for that somehow?

@ubearline thanks for testing!

sigmaSd commented 2 years ago

Can you add dbg!(term.width(),line_width) before the panic

konstin commented 2 years ago

Different code, same crash: https://gist.github.com/konstin/1e7450131fb59c216256ec1367f394b5

djc commented 2 years ago

Can you add the debug calls from the comment before yours?

sigmaSd commented 2 years ago

Also the crash happens also only with git bash ?

konstin commented 2 years ago

Also the crash happens also only with git bash ?

Initially I used a bash on linux in pycharm, but now it failed in the terminal but happened again when stepping through my code in the debugger. The bug seems really flaky in general

Can you add the debug calls from the comment before yours?

Here you go:

[indicatif/src/draw_target.rs:457] term.width() = 80
[indicatif/src/draw_target.rs:457] line_width = 40
aiohttp
Installing ░░░░░░░░░░░░░░░░░░░░   0/112                                         [indicatif/src/draw_target.rs:457] term.width() = 80
[indicatif/src/draw_target.rs:457] line_width = 51

Installing ░░░░░░░░░░░░░░░░░░░░   0/112 ["aiohttp"]                             [indicatif/src/draw_target.rs:457] term.width() = 80
[indicatif/src/draw_target.rs:457] line_width = 51

aiosignal
Installing ░░░░░░░░░░░░░░░░░░░░   0/112 ["aiohttp"]                             [indicatif/src/draw_target.rs:457] term.width() = 80
[indicatif/src/draw_target.rs:457] line_width = 64

Installing ░░░░░░░░░░░░░░░░░░░░   0/112 ["aiohttp", "aiosignal"]                [indicatif/src/draw_target.rs:457] term.width() = 80
[indicatif/src/draw_target.rs:457] line_width = 64

asgiref
Installing ░░░░░░░░░░░░░░░░░░░░   0/112 ["aiohttp", "aiosignal"]                [indicatif/src/draw_target.rs:457] term.width() = 80
[indicatif/src/draw_target.rs:457] line_width = 75

Installing ░░░░░░░░░░░░░░░░░░░░   0/112 ["aiohttp", "aiosignal", "asgiref"]     [indicatif/src/draw_target.rs:457] term.width() = 80
[indicatif/src/draw_target.rs:457] line_width = 75

async_timeout
Installing ░░░░░░░░░░░░░░░░░░░░   0/112 ["aiohttp", "aiosignal", "asgiref"]     [indicatif/src/draw_target.rs:457] term.width() = 80
[indicatif/src/draw_target.rs:457] line_width = 92
thread 'main' panicked at 'attempt to subtract with overflow', indicatif/src/draw_target.rs:458:44
stack backtrace:
   0: rust_begin_unwind
             at /rustc/02072b482a8b5357f7fb5e5637444ae30e423c40/library/std/src/panicking.rs:498:5
   1: core::panicking::panic_fmt
             at /rustc/02072b482a8b5357f7fb5e5637444ae30e423c40/library/core/src/panicking.rs:107:14
   2: core::panicking::panic
             at /rustc/02072b482a8b5357f7fb5e5637444ae30e423c40/library/core/src/panicking.rs:48:5
   3: indicatif::draw_target::ProgressDrawState::draw_to_term
             at ./indicatif/src/draw_target.rs:458:44
   4: indicatif::draw_target::Drawable::draw
             at ./indicatif/src/draw_target.rs:296:18
   5: indicatif::state::BarState::draw
             at ./indicatif/src/state.rs:127:9
   6: indicatif::state::BarState::update_and_draw
             at ./indicatif/src/state.rs:90:13
   7: indicatif::progress_bar::ProgressBar::update_and_draw
             at ./indicatif/src/progress_bar.rs:554:9
   8: indicatif::progress_bar::ProgressBar::set_message
             at ./indicatif/src/progress_bar.rs:318:9
   9: cli::main
             at ./src/main.rs:130:9
  10: core::ops::function::FnOnce::call_once
             at /rustc/02072b482a8b5357f7fb5e5637444ae30e423c40/library/core/src/ops/function.rs:227:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

Installing ░░░░░░░░░░░░░░░░░░░░   0/112 ["aiohttp", "aiosignal", "asgiref", "async_timeout"]

Process finished with exit code 101
djc commented 2 years ago

So I'm guessing it started installing a fourth package or a package with a longer name, after which the line width exceeded the terminal width, which we currently don't handle well?

sigmaSd commented 2 years ago

Its reproducible by just using a very long template, should be an easy fix for the panic itself

But I was curious though what indicatif does in these cases where the line width > terminal width, turns out indicatif just doesn't handle this case, and the line will wrap and became spammed.

What about if line > terminal.width() we replace the extra characters with "..."

chris-laplante commented 2 years ago

What about if line > terminal.width() we replace the extra characters with "..."

I like this idea. I think it would be pretty complicated to track progress bars that take up multiple lines.

djc commented 2 years ago

Seems reasonable!

konstin commented 2 years ago

That was quick, thank you!