Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 2 additions & 2 deletions nix/sources.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@
"type": "git"
},
"dfinity": {
"ref": "v0.5.4",
"ref": "master",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

is this only for testing?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Well, yes and no :)

I needed to point to a commit past v0.5.5 in dfinity to make things work for my demo last week. I'll use what I'm told here (presumably v0.5.6). Better yet, if someone bumps dfinity separately (say for a new SDK release) until this is merged and it includes the commit I'm interested in, I don't need to touch this at all.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Great, muting the thread without approving. Just ping us if you do need a change that needs an infra review!

"repo": "ssh://git@github.com/dfinity-lab/dfinity",
"rev": "a77551d2c630c2f30d18860bfb556ce46a0e8b08",
"rev": "915481642c6acd7bbfa7a4d674622bc5a2bc9fc2",
"type": "git"
},
"motoko": {
Expand Down
62 changes: 59 additions & 3 deletions src/dfx/src/commands/canister/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ use crate::lib::error::{DfxError, DfxResult};
use crate::lib::message::UserMessage;

use clap::{App, Arg, ArgMatches, SubCommand};
use ic_http_agent::{Agent, Blob, CanisterAttributes, ComputeAllocation, RequestId};
use ic_http_agent::{
Agent, Blob, CanisterAttributes, ComputeAllocation, MemoryAllocation, RequestId,
};
use slog::info;
use std::convert::TryInto;
use std::convert::{TryFrom, TryInto};
use tokio::runtime::Runtime;

pub fn construct() -> App<'static, 'static> {
Expand Down Expand Up @@ -42,13 +44,23 @@ pub fn construct() -> App<'static, 'static> {
.default_value("0")
.validator(compute_allocation_validator),
)
.arg(
Arg::with_name("memory-allocation")
.help(UserMessage::InstallMemoryAllocation.to_str())
.long("memory-allocation")
.short("m")
.takes_value(true)
.default_value("8GB")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do you want to duplicate the information about the default here? I think the two sensible design spaces are:

  • The system defines a default, dfx will, if no --memory-allocation is passed, send none, system default applies
  • The system defines no default, the field is mandatory, dfx determines (here) what the default is.

With this code, there are now two components both defining a default value; that’s odd.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I'm fine with the first approach.

Btw, the same happens for compute allocation and I don't remember why it was done like that.

With this code, there are now two components both defining a default value; that’s odd.

No, there's two components that follow the default value set by a common reference (the public spec) :) But yeah it's probably redundant since the replica will enforce the default anyway.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I wouldn’t consider it “following the public spec” if the client makes an optional field mandatory and hides the default value (which may change in later versions). But this is getting philosophical and we are in agreement :-)

Maybe the public spec shouldn’t be the place to define default values… oh well.

same happens for compute allocation

Yes, we should do the same for that too.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Maybe the public spec shouldn’t be the place to define default values… oh well.

This would mean that a concrete Internet Computer implementation could choose an arbitrary (within the specified range) default value if none is specified, which would mean that if I use dfx against two different implementations of the IC, then I get different behavior for the same request. Maybe it's cool, maybe it's not.

Anyway, I already fell in your trap

But this is getting philosophical and we are in agreement :-)

and as I said I'm fine with your first suggestion.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'd much prefer the first approach.

No, there's two components that follow the default value set by a common reference (the public spec) :)

I think the mistake is treating it as a "default value" at the entry point. The spec just says if the value isn't specified the system should treat it like 2^33. That's at the instanciation point. Technically we could not have a field at all and still be spec compliant.

The interface definition is clear that the field is optional, so there should be nothing on the wire.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Glad to see everyone in agreement :-)

.validator(memory_allocation_validator),
)
}

async fn install_canister(
env: &dyn Environment,
agent: &Agent,
canister_info: &CanisterInfo,
compute_allocation: ComputeAllocation,
memory_allocation: MemoryAllocation,
) -> DfxResult<RequestId> {
let log = env.get_logger();
let canister_id = canister_info.get_canister_id().ok_or_else(|| {
Expand All @@ -70,7 +82,10 @@ async fn install_canister(
&canister_id,
&Blob::from(wasm),
&Blob::empty(),
&CanisterAttributes { compute_allocation },
&CanisterAttributes {
compute_allocation,
memory_allocation,
},
)
.await
.map_err(DfxError::from)
Expand All @@ -85,6 +100,35 @@ fn compute_allocation_validator(compute_allocation: String) -> Result<(), String
Err("Must be a percent between 0 and 100".to_string())
}

fn parse_memory_allocation(memory_allocation: String) -> Result<u64, String> {
Comment thread
dsarlis marked this conversation as resolved.
Outdated
let split_point = memory_allocation.find(|c: char| !c.is_numeric());
let memory_allocation = memory_allocation.trim();
let (raw_num, unit) = split_point.map_or_else(
|| (memory_allocation, ""),
|p| memory_allocation.split_at(p),
);
let raw_num = raw_num
.parse::<u64>()
.map_err(|_| format!("Could not parse number: {}", raw_num))?;
let unit = unit.trim();
match unit {
"KB" => Ok(raw_num * 1024),
"MB" => Ok(raw_num * 1024 * 1024),
"GB" => Ok(raw_num * 1024 * 1024 * 1024),
_ => return Err(format!("Invalid unit for memory allocation, {}. Expected one of <KB|MB|GB>", unit)),
}
}

fn memory_allocation_validator(memory_allocation: String) -> Result<(), String> {
let num = parse_memory_allocation(memory_allocation)?;

if num <= (1 << 48) {
Ok(())
} else {
Err("Must be a number of bytes between 0 and 2^48".to_string())
}
}

pub fn exec(env: &dyn Environment, args: &ArgMatches<'_>) -> DfxResult {
let log = env.get_logger();
let config = env
Expand All @@ -101,6 +145,16 @@ pub fn exec(env: &dyn Environment, args: &ArgMatches<'_>) -> DfxResult {
.try_into()
.expect("Compute Allocation must be a percentage.");

let memory_allocation = MemoryAllocation::try_from(
parse_memory_allocation(
args.value_of("memory-allocation")
.unwrap_or("8GB")
.to_string(),
)
.unwrap(),
)
.expect("Memory Allocation must be a number between 0 and 2^48");

let mut runtime = Runtime::new().expect("Unable to create a runtime");

if let Some(canister_name) = args.value_of("canister_name") {
Expand All @@ -110,6 +164,7 @@ pub fn exec(env: &dyn Environment, args: &ArgMatches<'_>) -> DfxResult {
&agent,
&canister_info,
compute_allocation,
memory_allocation,
))?;

if args.is_present("async") {
Expand All @@ -132,6 +187,7 @@ pub fn exec(env: &dyn Environment, args: &ArgMatches<'_>) -> DfxResult {
&agent,
&canister_info,
compute_allocation,
memory_allocation,
))?;

if args.is_present("async") {
Expand Down
1 change: 1 addition & 0 deletions src/dfx/src/lib/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ user_message!(
InstallAll => "Install all canisters configured in dfx.json.",
InstallCanisterName => "Specifies the canister name. Either this or the --all flag are required.",
InstallComputeAllocation => "Specifies the canister's compute allocation. This should be a percent in the range [0..100]",
InstallMemoryAllocation => "Specifies the canister's memory allocation in bytes. This should be a number in the range [0..2^48]",

// dfx canister mod
ManageCanister => "Manages canisters deployed on a network client.",
Expand Down
1 change: 1 addition & 0 deletions src/ic_http_agent/src/agent/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ impl Agent {
arg,
nonce: &self.nonce_factory.generate(),
compute_allocation: attributes.compute_allocation.0,
memory_allocation: attributes.memory_allocation.0,
})
.await
}
Expand Down
1 change: 1 addition & 0 deletions src/ic_http_agent/src/agent/replica_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ pub enum SubmitRequest<'a> {
arg: &'a Blob,
nonce: &'a Option<Blob>,
compute_allocation: u8,
memory_allocation: u64,
},
Call {
canister_id: &'a CanisterId,
Expand Down
38 changes: 38 additions & 0 deletions src/ic_http_agent/src/types/canister_attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,52 @@ impl std::convert::TryFrom<u8> for ComputeAllocation {
}
}

#[derive(Clone, Debug)]
Comment thread
dsarlis marked this conversation as resolved.
pub enum MemoryAllocationError {
ValueOutOfRange,
}

impl std::fmt::Display for MemoryAllocationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
match self {
MemoryAllocationError::ValueOutOfRange => {
f.write_str("Must be a number between 0 and 2^48.")
}
}
}
}

#[derive(Copy, Clone, Debug)]
pub struct MemoryAllocation(pub(crate) u64);

impl std::convert::From<MemoryAllocation> for u64 {
fn from(memory_allocation: MemoryAllocation) -> Self {
memory_allocation.0
}
}

impl std::convert::TryFrom<u64> for MemoryAllocation {
type Error = MemoryAllocationError;

fn try_from(value: u64) -> Result<Self, Self::Error> {
if value > (1 << 48) {
Err(MemoryAllocationError::ValueOutOfRange)
} else {
Ok(Self(value))
}
}
}

pub struct CanisterAttributes {
pub compute_allocation: ComputeAllocation,
pub memory_allocation: MemoryAllocation,
Comment thread
dsarlis marked this conversation as resolved.
Outdated
}

impl Default for CanisterAttributes {
fn default() -> Self {
CanisterAttributes {
compute_allocation: ComputeAllocation(0),
memory_allocation: MemoryAllocation(8 * 1024 * 1024 * 1024),
Comment thread
dsarlis marked this conversation as resolved.
Outdated
}
}
}
5 changes: 4 additions & 1 deletion src/ic_http_agent/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ pub(crate) mod public {
use super::*;

pub use blob::Blob;
pub use canister_attributes::{CanisterAttributes, ComputeAllocation, ComputeAllocationError};
pub use canister_attributes::{
CanisterAttributes, ComputeAllocation, ComputeAllocationError, MemoryAllocation,
MemoryAllocationError,
};
pub use canister_id::{CanisterId, TextualCanisterIdError};
pub use principal::Principal;
pub use request_id::{to_request_id, RequestId};
Expand Down