1use std::cell::{Cell, RefCell};
19use std::collections::HashMap;
20use std::future::Future;
21use std::pin::Pin;
22use std::rc::Rc;
23use std::sync::Arc;
24use std::task::{Context, Poll, Waker};
25
26use day_spec::{
27 BitmapId, BitmapInfo, Cap, DrawOp, EncodeSpec, ExportError, ImageError, ImageFormat,
28 ImageProperties, PdfInfo, PdfPage, Size,
29};
30
31use crate::with_tree;
32
33day_reactive::tls_slots! {
34 image;
35 static PENDING_DECODE: RefCell<HashMap<u64, (BitmapId, Pending<(BitmapId, BitmapInfo)>)>> =
39 RefCell::new(HashMap::new());
40 static PENDING_ENCODE: RefCell<HashMap<u64, Pending<Vec<u8>>>> = RefCell::new(HashMap::new());
41 static PENDING_BITMAP: RefCell<HashMap<u64, (BitmapId, Waiting<(BitmapId, BitmapInfo), ExportError>)>> =
43 RefCell::new(HashMap::new());
44 static PENDING_PDF: RefCell<HashMap<u64, Waiting<Vec<u8>, ExportError>>> =
45 RefCell::new(HashMap::new());
46 static DEFERRED_RELEASE: RefCell<Vec<BitmapId>> = RefCell::new(Vec::new());
49 static NEXT_REQ: Cell<u64> = const { Cell::new(1) };
50 static EXPORTS: Cell<Option<CanvasExports>> = const { Cell::new(None) };
54 static NEXT_BITMAP: Cell<u64> = const { Cell::new(1) };
58}
59
60type ImageDone<T, E = ImageError> = Box<dyn FnOnce(Result<T, E>)>;
66
67struct Waiting<T, E> {
70 shared: Rc<Shared<T, E>>,
71 callback: Option<ImageDone<T, E>>,
72}
73
74type Pending<T> = Waiting<T, ImageError>;
76
77struct Shared<T, E = ImageError> {
78 result: RefCell<Option<Result<T, E>>>,
79 waker: RefCell<Option<Waker>>,
80 cancelled: Cell<bool>,
83}
84
85impl<T, E> Default for Shared<T, E> {
86 fn default() -> Self {
87 Shared {
88 result: RefCell::new(None),
89 waker: RefCell::new(None),
90 cancelled: Cell::new(false),
91 }
92 }
93}
94
95fn next_req() -> u64 {
96 NEXT_REQ.with(|c| {
97 let v = c.get();
98 c.set(v + 1);
99 v
100 })
101}
102
103fn next_bitmap() -> BitmapId {
104 BitmapId(NEXT_BITMAP.with(|c| {
105 let v = c.get();
106 c.set(v + 1);
107 v
108 }))
109}
110
111#[derive(Clone)]
120pub struct Bitmap(Rc<BitmapInner>);
121
122struct BitmapInner {
123 id: BitmapId,
124 info: BitmapInfo,
125}
126
127impl Drop for BitmapInner {
128 fn drop(&mut self) {
129 release(self.id);
130 }
131}
132
133fn release(id: BitmapId) {
141 if crate::tree_absent() {
142 return;
143 }
144 if crate::with_tree_if_free(|t| t.release_image(id)).is_none() {
145 DEFERRED_RELEASE.with(|q| q.borrow_mut().push(id));
146 crate::request_pump();
147 }
148}
149
150pub(crate) fn flush_deferred_releases() {
154 let ids: Vec<BitmapId> = DEFERRED_RELEASE.with(|q| std::mem::take(&mut *q.borrow_mut()));
155 for id in ids {
156 if crate::with_tree_if_free(|t| t.release_image(id)).is_none() {
157 DEFERRED_RELEASE.with(|q| q.borrow_mut().push(id));
158 }
159 }
160}
161
162impl Bitmap {
163 pub fn id(&self) -> BitmapId {
165 self.0.id
166 }
167
168 pub fn info(&self) -> BitmapInfo {
170 self.0.info
171 }
172
173 pub fn properties(&self) -> Option<ImageProperties> {
177 let id = self.0.id;
178 with_tree(|t| t.image_properties(id))
179 }
180
181 pub fn encode(&self, spec: EncodeSpec) -> EncodeFuture {
183 EncodeFuture {
184 source: self.clone(),
185 spec,
186 shared: Rc::new(Shared::default()),
187 started: false,
188 }
189 }
190
191 pub fn encode_async(
193 &self,
194 spec: EncodeSpec,
195 on_done: impl FnOnce(Result<Vec<u8>, ImageError>) + 'static,
196 ) {
197 if support(Cap::ImageEncode) == day_spec::Support::Unsupported {
198 on_done(Err(ImageError::Unsupported));
199 return;
200 }
201 let keep = self.clone();
204 start_encode(
205 self.0.id,
206 spec,
207 None,
208 Some(Box::new(move |r| {
209 drop(keep);
210 on_done(r)
211 })),
212 );
213 }
214}
215
216impl std::fmt::Debug for Bitmap {
217 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
218 f.debug_struct("Bitmap")
219 .field("id", &self.0.id.0)
220 .field("info", &self.0.info)
221 .finish()
222 }
223}
224
225impl PartialEq for Bitmap {
227 fn eq(&self, other: &Self) -> bool {
228 self.0.id == other.0.id
229 }
230}
231
232pub fn decode(bytes: Arc<Vec<u8>>) -> DecodeFuture {
251 DecodeFuture {
252 bytes: Some(bytes),
253 shared: Rc::new(Shared::default()),
254 started: false,
255 }
256}
257
258pub fn decode_async(
260 bytes: Arc<Vec<u8>>,
261 on_done: impl FnOnce(Result<Bitmap, ImageError>) + 'static,
262) {
263 if support(Cap::ImageDecode) == day_spec::Support::Unsupported {
264 on_done(Err(ImageError::Unsupported));
265 return;
266 }
267 start_decode(
268 &bytes,
269 None,
270 Some(Box::new(move |r| on_done(r.map(bitmap_from)))),
271 );
272}
273
274pub fn image_decode_support() -> day_spec::Support {
276 support(Cap::ImageDecode)
277}
278
279pub fn image_encode_support() -> day_spec::Support {
281 support(Cap::ImageEncode)
282}
283
284pub fn image_encode_formats() -> Vec<ImageFormat> {
287 with_tree(|t| t.image_encode_formats())
288}
289
290fn support(cap: Cap) -> day_spec::Support {
291 with_tree(|t| t.capability(cap))
292}
293
294fn bitmap_from((id, info): (BitmapId, BitmapInfo)) -> Bitmap {
295 Bitmap(Rc::new(BitmapInner { id, info }))
296}
297
298fn start_decode(
300 bytes: &[u8],
301 shared: Option<Rc<Shared<(BitmapId, BitmapInfo)>>>,
302 callback: Option<ImageDone<(BitmapId, BitmapInfo)>>,
303) -> u64 {
304 let req = next_req();
305 let id = next_bitmap();
306 PENDING_DECODE.with(|p| {
307 p.borrow_mut().insert(
308 req,
309 (
310 id,
311 Pending {
312 shared: shared.unwrap_or_default(),
313 callback,
314 },
315 ),
316 )
317 });
318 with_tree(|t| t.decode_image(req, id, bytes));
319 req
320}
321
322fn start_encode(
323 id: BitmapId,
324 spec: EncodeSpec,
325 shared: Option<Rc<Shared<Vec<u8>>>>,
326 callback: Option<ImageDone<Vec<u8>>>,
327) -> u64 {
328 let req = next_req();
329 PENDING_ENCODE.with(|p| {
330 p.borrow_mut().insert(
331 req,
332 Pending {
333 shared: shared.unwrap_or_default(),
334 callback,
335 },
336 )
337 });
338 with_tree(|t| t.encode_image(req, id, &spec));
339 req
340}
341
342pub struct DecodeFuture {
344 bytes: Option<Arc<Vec<u8>>>,
345 shared: Rc<Shared<(BitmapId, BitmapInfo)>>,
346 started: bool,
347}
348
349impl Future for DecodeFuture {
350 type Output = Result<Bitmap, ImageError>;
351
352 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
353 if let Some(result) = self.shared.result.borrow_mut().take() {
354 return Poll::Ready(result.map(bitmap_from));
355 }
356 if !self.started {
357 self.started = true;
358 if support(Cap::ImageDecode) == day_spec::Support::Unsupported {
359 return Poll::Ready(Err(ImageError::Unsupported));
360 }
361 let bytes = self.bytes.take().expect("decode bytes");
362 let shared = self.shared.clone();
363 start_decode(&bytes, Some(shared), None);
364 if let Some(result) = self.shared.result.borrow_mut().take() {
370 return Poll::Ready(result.map(bitmap_from));
371 }
372 }
373 *self.shared.waker.borrow_mut() = Some(cx.waker().clone());
374 Poll::Pending
375 }
376}
377
378impl Drop for DecodeFuture {
379 fn drop(&mut self) {
380 if let Some(Ok((id, _))) = self.shared.result.borrow_mut().take() {
383 release(id);
384 }
385 self.shared.cancelled.set(true);
388 }
389}
390
391pub struct EncodeFuture {
393 source: Bitmap,
396 spec: EncodeSpec,
397 shared: Rc<Shared<Vec<u8>>>,
398 started: bool,
399}
400
401impl Future for EncodeFuture {
402 type Output = Result<Vec<u8>, ImageError>;
403
404 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
405 if let Some(result) = self.shared.result.borrow_mut().take() {
406 return Poll::Ready(result);
407 }
408 if !self.started {
409 self.started = true;
410 if support(Cap::ImageEncode) == day_spec::Support::Unsupported {
411 return Poll::Ready(Err(ImageError::Unsupported));
412 }
413 let shared = self.shared.clone();
414 start_encode(self.source.id(), self.spec, Some(shared), None);
415 if let Some(result) = self.shared.result.borrow_mut().take() {
417 return Poll::Ready(result);
418 }
419 }
420 *self.shared.waker.borrow_mut() = Some(cx.waker().clone());
421 Poll::Pending
422 }
423}
424
425pub fn resolve_image_decode(req: u64, result: Result<BitmapInfo, ImageError>) {
432 let Some((id, entry)) = PENDING_DECODE.with(|p| p.borrow_mut().remove(&req)) else {
433 return;
434 };
435 if entry.callback.is_none() && entry.shared.cancelled.get() {
438 if result.is_ok() {
439 release(id);
440 }
441 return;
442 }
443 deliver(entry, result.map(|info| (id, info)));
444}
445
446pub fn resolve_image_encode(req: u64, result: Result<Vec<u8>, ImageError>) {
448 let Some(entry) = PENDING_ENCODE.with(|p| p.borrow_mut().remove(&req)) else {
449 return;
450 };
451 deliver(entry, result);
452}
453
454fn deliver<T, E>(entry: Waiting<T, E>, value: Result<T, E>) {
455 if let Some(cb) = entry.callback {
456 cb(value);
457 return;
458 }
459 *entry.shared.result.borrow_mut() = Some(value);
460 if let Some(waker) = entry.shared.waker.borrow_mut().take() {
461 waker.wake();
462 }
463}
464
465pub(crate) fn reset() {
469 let decodes: Vec<_> =
470 PENDING_DECODE.with(|p| p.borrow_mut().drain().map(|(_, (_, e))| e).collect());
471 for entry in decodes {
472 deliver(entry, Err(ImageError::Unsupported));
473 }
474 let encodes: Vec<_> = PENDING_ENCODE.with(|p| p.borrow_mut().drain().map(|(_, e)| e).collect());
475 for entry in encodes {
476 deliver(entry, Err(ImageError::Unsupported));
477 }
478 let bitmaps: Vec<_> =
479 PENDING_BITMAP.with(|p| p.borrow_mut().drain().map(|(_, (_, e))| e).collect());
480 for entry in bitmaps {
481 deliver(entry, Err(ExportError::Unsupported));
482 }
483 let pdfs: Vec<_> = PENDING_PDF.with(|p| p.borrow_mut().drain().map(|(_, e)| e).collect());
484 for entry in pdfs {
485 deliver(entry, Err(ExportError::Unsupported));
486 }
487 DEFERRED_RELEASE.with(|q| q.borrow_mut().clear());
488 EXPORTS.with(|c| c.set(None));
489}
490
491#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
499pub struct CanvasExports {
500 pub bitmap: bool,
503 pub pdf: bool,
505 png: bool,
506}
507
508impl CanvasExports {
509 pub fn png(&self) -> bool {
511 self.png
512 }
513}
514
515pub fn canvas_exports() -> CanvasExports {
522 if let Some(known) = EXPORTS.with(|c| c.get()) {
523 return known;
524 }
525 let Some(read) = crate::with_tree_if_free(|t| {
526 let yes = |cap| t.capability(cap) != day_spec::Support::Unsupported;
527 let bitmap = yes(Cap::CanvasBitmap);
528 CanvasExports {
529 bitmap,
530 pdf: yes(Cap::CanvasPdf),
531 png: bitmap && t.image_encode_formats().contains(&ImageFormat::Png),
532 }
533 }) else {
534 return CanvasExports::default();
535 };
536 EXPORTS.with(|c| c.set(Some(read)));
537 read
538}
539
540fn check_bitmap(size: Size, scale: f64) -> Result<(), ExportError> {
543 let positive = |v: f64| v.is_finite() && v > 0.0;
544 if !positive(size.width) || !positive(size.height) || !positive(scale) {
545 return Err(ExportError::Invalid);
546 }
547 let (width, height) = day_spec::export_pixels(size, scale);
548 if width > day_spec::EXPORT_MAX_SIDE
549 || height > day_spec::EXPORT_MAX_SIDE
550 || u64::from(width) * u64::from(height) > day_spec::EXPORT_MAX_PIXELS
551 {
552 return Err(ExportError::TooLarge { width, height });
553 }
554 Ok(())
555}
556
557fn check_pdf(pages: &[PdfPage]) -> Result<(), ExportError> {
558 let positive = |v: f64| v.is_finite() && v > 0.0;
559 if pages.is_empty()
560 || pages
561 .iter()
562 .any(|p| !positive(p.size.width) || !positive(p.size.height))
563 {
564 return Err(ExportError::Invalid);
565 }
566 Ok(())
567}
568
569fn admit_bitmap(size: Size, scale: f64) -> Result<(), ExportError> {
571 if support(Cap::CanvasBitmap) == day_spec::Support::Unsupported {
572 return Err(ExportError::Unsupported);
573 }
574 check_bitmap(size, scale)
575}
576
577fn admit_pdf(pages: &[PdfPage]) -> Result<(), ExportError> {
578 if support(Cap::CanvasPdf) == day_spec::Support::Unsupported {
579 return Err(ExportError::Unsupported);
580 }
581 check_pdf(pages)
582}
583
584pub fn render_bitmap(ops: Vec<DrawOp>, size: Size, scale: f64) -> RenderBitmap {
589 RenderBitmap {
590 job: Some((ops, size, scale)),
591 shared: Rc::new(Shared::default()),
592 started: false,
593 }
594}
595
596pub fn render_bitmap_async(
598 ops: Vec<DrawOp>,
599 size: Size,
600 scale: f64,
601 on_done: impl FnOnce(Result<Bitmap, ExportError>) + 'static,
602) {
603 if let Err(e) = admit_bitmap(size, scale) {
604 on_done(Err(e));
605 return;
606 }
607 start_bitmap(
608 &ops,
609 size,
610 scale,
611 None,
612 Some(Box::new(move |r| on_done(r.map(bitmap_from)))),
613 );
614}
615
616pub fn render_pdf(pages: Vec<PdfPage>, info: PdfInfo) -> RenderPdf {
619 RenderPdf {
620 job: Some((pages, info)),
621 shared: Rc::new(Shared::default()),
622 started: false,
623 }
624}
625
626pub fn render_pdf_async(
628 pages: Vec<PdfPage>,
629 info: PdfInfo,
630 on_done: impl FnOnce(Result<Vec<u8>, ExportError>) + 'static,
631) {
632 if let Err(e) = admit_pdf(&pages) {
633 on_done(Err(e));
634 return;
635 }
636 start_pdf(&pages, &info, None, Some(Box::new(on_done)));
637}
638
639fn start_bitmap(
640 ops: &[DrawOp],
641 size: Size,
642 scale: f64,
643 shared: Option<Rc<Shared<(BitmapId, BitmapInfo), ExportError>>>,
644 callback: Option<ImageDone<(BitmapId, BitmapInfo), ExportError>>,
645) {
646 let req = next_req();
647 let id = next_bitmap();
648 PENDING_BITMAP.with(|p| {
649 p.borrow_mut().insert(
650 req,
651 (
652 id,
653 Waiting {
654 shared: shared.unwrap_or_default(),
655 callback,
656 },
657 ),
658 )
659 });
660 with_tree(|t| t.render_bitmap(req, id, ops, size, scale));
661}
662
663fn start_pdf(
664 pages: &[PdfPage],
665 info: &PdfInfo,
666 shared: Option<Rc<Shared<Vec<u8>, ExportError>>>,
667 callback: Option<ImageDone<Vec<u8>, ExportError>>,
668) {
669 let req = next_req();
670 PENDING_PDF.with(|p| {
671 p.borrow_mut().insert(
672 req,
673 Waiting {
674 shared: shared.unwrap_or_default(),
675 callback,
676 },
677 )
678 });
679 with_tree(|t| t.render_pdf(req, pages, info));
680}
681
682pub struct RenderBitmap {
685 job: Option<(Vec<DrawOp>, Size, f64)>,
686 shared: Rc<Shared<(BitmapId, BitmapInfo), ExportError>>,
687 started: bool,
688}
689
690impl Future for RenderBitmap {
691 type Output = Result<Bitmap, ExportError>;
692
693 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
694 if let Some(result) = self.shared.result.borrow_mut().take() {
695 return Poll::Ready(result.map(bitmap_from));
696 }
697 if !self.started {
698 self.started = true;
699 let Some((ops, size, scale)) = self.job.take() else {
700 return Poll::Ready(Err(ExportError::Invalid));
701 };
702 if let Err(e) = admit_bitmap(size, scale) {
703 return Poll::Ready(Err(e));
704 }
705 start_bitmap(&ops, size, scale, Some(self.shared.clone()), None);
706 if let Some(result) = self.shared.result.borrow_mut().take() {
708 return Poll::Ready(result.map(bitmap_from));
709 }
710 }
711 *self.shared.waker.borrow_mut() = Some(cx.waker().clone());
712 Poll::Pending
713 }
714}
715
716impl Drop for RenderBitmap {
717 fn drop(&mut self) {
718 if let Some(Ok((id, _))) = self.shared.result.borrow_mut().take() {
720 release(id);
721 }
722 self.shared.cancelled.set(true);
723 }
724}
725
726pub struct RenderPdf {
729 job: Option<(Vec<PdfPage>, PdfInfo)>,
730 shared: Rc<Shared<Vec<u8>, ExportError>>,
731 started: bool,
732}
733
734impl Future for RenderPdf {
735 type Output = Result<Vec<u8>, ExportError>;
736
737 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
738 if let Some(result) = self.shared.result.borrow_mut().take() {
739 return Poll::Ready(result);
740 }
741 if !self.started {
742 self.started = true;
743 let Some((pages, info)) = self.job.take() else {
744 return Poll::Ready(Err(ExportError::Invalid));
745 };
746 if let Err(e) = admit_pdf(&pages) {
747 return Poll::Ready(Err(e));
748 }
749 start_pdf(&pages, &info, Some(self.shared.clone()), None);
750 if let Some(result) = self.shared.result.borrow_mut().take() {
751 return Poll::Ready(result);
752 }
753 }
754 *self.shared.waker.borrow_mut() = Some(cx.waker().clone());
755 Poll::Pending
756 }
757}
758
759pub fn resolve_bitmap_render(req: u64, result: Result<BitmapInfo, ExportError>) {
762 let Some((id, entry)) = PENDING_BITMAP.with(|p| p.borrow_mut().remove(&req)) else {
763 return;
764 };
765 if entry.callback.is_none() && entry.shared.cancelled.get() {
766 if result.is_ok() {
767 release(id);
768 }
769 return;
770 }
771 deliver(entry, result.map(|info| (id, info)));
772}
773
774pub fn resolve_pdf_render(req: u64, result: Result<Vec<u8>, ExportError>) {
776 let Some(entry) = PENDING_PDF.with(|p| p.borrow_mut().remove(&req)) else {
777 return;
778 };
779 deliver(entry, result);
780}
781
782#[cfg(test)]
783mod export_tests {
784 use super::*;
785
786 #[test]
787 fn bitmap_sizes_are_checked_before_the_toolkit() {
788 let size = Size::new(100.0, 50.0);
789 assert_eq!(check_bitmap(size, 2.0), Ok(()));
790 assert_eq!(
791 check_bitmap(Size::new(0.0, 50.0), 1.0),
792 Err(ExportError::Invalid)
793 );
794 assert_eq!(check_bitmap(size, f64::NAN), Err(ExportError::Invalid));
795 assert_eq!(check_bitmap(size, -1.0), Err(ExportError::Invalid));
796 assert_eq!(
797 check_bitmap(Size::new(16_385.0, 1.0), 1.0),
798 Err(ExportError::TooLarge {
799 width: 16_385,
800 height: 1
801 })
802 );
803 assert_eq!(
805 check_bitmap(Size::new(5_000.0, 5_000.0), 2.0),
806 Err(ExportError::TooLarge {
807 width: 10_000,
808 height: 10_000
809 })
810 );
811 assert_eq!(day_spec::export_pixels(Size::new(10.2, 3.0), 2.0), (21, 6));
813 }
814
815 #[test]
816 fn a_pdf_needs_pages_with_a_size() {
817 assert_eq!(check_pdf(&[]), Err(ExportError::Invalid));
818 let page = |w, h| PdfPage {
819 size: Size::new(w, h),
820 ops: Vec::new(),
821 };
822 assert_eq!(check_pdf(&[page(612.0, 792.0)]), Ok(()));
823 assert_eq!(
824 check_pdf(&[page(612.0, 792.0), page(0.0, 10.0)]),
825 Err(ExportError::Invalid)
826 );
827 }
828}
829
830#[cfg(test)]
831mod teardown_tests {
832 use super::*;
833
834 #[test]
835 fn bitmap_drop_during_reactive_tls_teardown() {
836 const CHILD: &str = "DAY_CORE_BITMAP_REACTIVE_TEARDOWN_CHILD";
837 if let Some(mode) = std::env::var_os(CHILD) {
838 struct TeardownBitmap {
839 bitmap: Option<Bitmap>,
840 dropped: std::sync::mpsc::Sender<()>,
841 }
842 impl Drop for TeardownBitmap {
843 fn drop(&mut self) {
844 drop(self.bitmap.take());
845 self.dropped.send(()).unwrap();
846 }
847 }
848 let (dropped, observed) = std::sync::mpsc::channel();
849 std::thread::spawn(move || {
850 day_reactive::Signal::global(TeardownBitmap {
853 bitmap: Some(bitmap_from((BitmapId(1), BitmapInfo::default()))),
854 dropped,
855 });
856 if mode == "initialized" {
857 assert!(crate::try_tls_root(|_| ()).is_ok());
860 }
861 })
862 .join()
863 .unwrap();
864 observed
865 .recv_timeout(std::time::Duration::from_secs(5))
866 .expect("bitmap was not dropped during reactive TLS teardown");
867 return;
868 }
869 for mode in ["uninitialized", "initialized"] {
872 let output = std::process::Command::new(std::env::current_exe().unwrap())
873 .args([
874 "--exact",
875 "image::teardown_tests::bitmap_drop_during_reactive_tls_teardown",
876 "--nocapture",
877 ])
878 .env(CHILD, mode)
879 .output()
880 .unwrap();
881 assert!(
882 output.status.success(),
883 "{mode}: {}",
884 String::from_utf8_lossy(&output.stderr)
885 );
886 }
887 }
888
889 #[test]
890 fn bitmap_drop_during_core_tls_teardown() {
891 const CHILD: &str = "DAY_CORE_BITMAP_TEARDOWN_CHILD";
892 if std::env::var_os(CHILD).is_some() {
893 struct TeardownBitmap {
894 bitmap: Option<Bitmap>,
895 dropped: std::sync::mpsc::Sender<()>,
896 }
897 impl Drop for TeardownBitmap {
898 fn drop(&mut self) {
899 assert!(crate::try_tls_root(|_| ()).is_err());
902 drop(self.bitmap.take());
903 self.dropped.send(()).unwrap();
904 }
905 }
906 let (dropped, observed) = std::sync::mpsc::channel();
907 std::thread::spawn(move || {
908 let held = TeardownBitmap {
909 bitmap: Some(bitmap_from((BitmapId(1), BitmapInfo::default()))),
910 dropped,
911 };
912 PENDING_ENCODE.with(|pending| {
915 pending.borrow_mut().insert(
916 1,
917 Pending {
918 shared: Rc::new(Shared::default()),
919 callback: Some(Box::new(move |_| drop(held))),
920 },
921 );
922 });
923 assert!(crate::try_tls_root(|_| ()).is_ok());
924 })
925 .join()
926 .unwrap();
927 observed
930 .recv_timeout(std::time::Duration::from_secs(5))
931 .expect("bitmap was not dropped during core TLS teardown");
932 return;
933 }
934 let output = std::process::Command::new(std::env::current_exe().unwrap())
935 .args([
936 "--exact",
937 "image::teardown_tests::bitmap_drop_during_core_tls_teardown",
938 "--nocapture",
939 ])
940 .env(CHILD, "1")
941 .output()
942 .unwrap();
943 assert!(
944 output.status.success(),
945 "{}",
946 String::from_utf8_lossy(&output.stderr)
947 );
948 }
949}