Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions frame/support/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ pub mod inherent;
pub mod error;
pub mod instances;
pub mod migrations;
pub mod sync;
pub mod traits;
pub mod weights;

Expand Down
43 changes: 18 additions & 25 deletions frame/support/src/storage/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,46 +48,39 @@ pub mod unhashed;
pub mod weak_bounded_vec;

mod transaction_level_tracker {
use core::sync::atomic::{AtomicU32, Ordering};
use crate::sync::RuntimeCell;

type Layer = u32;
static NUM_LEVELS: AtomicU32 = AtomicU32::new(0);
static NUM_LEVELS: RuntimeCell<u32> = RuntimeCell::new(0);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As long as we have the native runtime, this isn't safe. The native runtime can be executed from different threads.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really? Multiple threads operate on the same memory at the same time? Why would that be the case?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Native runtime. This static is shared within the entire context of the binary.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure but you still don't call into any runtime code concurrently with more than one thread? That would make no sense.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not the runtime itself, however you can call from different threads into the runtime. Imagine some thread importing a block and another validating an extrinsic. Both will call into the runtime

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume in the wasm case we spawn different instances for those calls which are then called in parallel.

To me it sounds like those calls need to be serialized to have the same behavior as with the wasm runtime.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do you want to serialize them? The point is, we don't use any global context and if we use it it needs to be thread local.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay I get it now. The native runtime recycles the memory from other calls while the wasm gets fresh memory. Yeah this makes this wrong, unfortunately.

const TRANSACTIONAL_LIMIT: Layer = 255;

pub fn get_transaction_level() -> Layer {
NUM_LEVELS.load(Ordering::SeqCst)
NUM_LEVELS.get()
}

/// Increments the transaction level. Returns an error if levels go past the limit.
///
/// Returns a guard that when dropped decrements the transaction level automatically.
pub fn inc_transaction_level() -> Result<StorageLayerGuard, ()> {
NUM_LEVELS
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |existing_levels| {
if existing_levels >= TRANSACTIONAL_LIMIT {
return None
}
// Cannot overflow because of check above.
Some(existing_levels + 1)
})
.map_err(|_| ())?;
let existing_levels = get_transaction_level();
if existing_levels >= TRANSACTIONAL_LIMIT {
return Err(())
}
// Cannot overflow because of check above.
NUM_LEVELS.set(existing_levels + 1);
Ok(StorageLayerGuard)
}

fn dec_transaction_level() {
NUM_LEVELS
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |existing_levels| {
if existing_levels == 0 {
log::warn!(
"We are underflowing with calculating transactional levels. Not great, but let's not panic...",
);
None
} else {
// Cannot underflow because of checks above.
Some(existing_levels - 1)
}
})
.ok();
let existing_levels = get_transaction_level();
if existing_levels == 0 {
log::warn!(
"We are underflowing with calculating transactional levels. Not great, but let's not panic...",
);
} else {
// Cannot underflow because of checks above.
NUM_LEVELS.set(existing_levels - 1);
}
}

pub fn is_transactional() -> bool {
Expand Down
76 changes: 76 additions & 0 deletions frame/support/src/sync.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// This file is part of Substrate.

// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Types used to sync access to shared data. All of these types rely on the assumption
//! that no data parallelism exists within the runtime.

use sp_std::{
cell::{Cell, RefCell},
ops::{Deref, DerefMut},
};

/// A [`Sync`] version of [`Cell`]. Safe to use within the runtime.
pub struct RuntimeCell<T>(Cell<T>);

impl<T> RuntimeCell<T> {
/// See [`Cell::new`]
pub const fn new(value: T) -> Self {
Self(Cell::new(value))
}
}

unsafe impl<T> Sync for RuntimeCell<T> {}

impl<T> Deref for RuntimeCell<T> {
type Target = Cell<T>;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl<T> DerefMut for RuntimeCell<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}

/// A [`Sync`] version of [`RefCell`]. Safe to use within the runtime.
pub struct RuntimeRefCell<T>(RefCell<T>);

impl<T> RuntimeRefCell<T> {
/// See [`RefCell::new`]
pub const fn new(value: T) -> Self {
Self(RefCell::new(value))
}
}

unsafe impl<T> Sync for RuntimeRefCell<T> {}

impl<T> Deref for RuntimeRefCell<T> {
type Target = RefCell<T>;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl<T> DerefMut for RuntimeRefCell<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}