Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support setting flow control limits for individual stream types #3948

Merged
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
5 changes: 4 additions & 1 deletion docs/Settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ The following settings are available via registry as well as via [QUIC_SETTINGS]
| Idle Timeout | uint64_t | IdleTimeoutMs | 30,000 | How long a connection can go idle before it is silently shut down. 0 to disable timeout |
| Max TLS Send Buffer (Client) | uint32_t | TlsClientMaxSendBuffer | 4,096 | How much client TLS data to buffer. |
| Max TLS Send Buffer (Server) | uint32_t | TlsServerMaxSendBuffer | 8,192 | How much server TLS data to buffer. |
| Stream Receive Window | uint32_t | StreamRecvWindowDefault | 32,768 | Initial stream receive window size. |
| Stream Receive Window | uint32_t | StreamRecvWindowDefault | 65,536 | Initial stream receive window size for all stream types. |
| Stream Receive Window (Bidirectional, locally created) | uint32_t | StreamRecvWindowBidiLocalDefault | - | If set, overrides stream receive window size for locally initiated bidirectional streams. |
| Stream Receive Window (Bidirectional, remotely created) | uint32_t | StreamRecvWindowBidiRemoteDefault | - | If set, overrides stream receive window size for remote initiated bidirectional streams. |
| Stream Receive Window (Unidirectional) | uint32_t | StreamRecvWindowUnidiDefault | - | If set, overrides stream receive window size for remote initiated unidirectional streams. |
| Stream Receive Buffer | uint32_t | StreamRecvBufferDefault | 4,096 | Stream initial buffer size. |
| Flow Control Window | uint32_t | ConnFlowControlWindow | 16,777,216 | Connection-wide flow control window. |
| Max Stateless Operations | uint32_t | MaxStatelessOperations | 16 | The maximum number of stateless operations that may be queued on a worker at any one time. |
Expand Down
52 changes: 49 additions & 3 deletions docs/api/QUIC_SETTINGS.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,18 @@ typedef struct QUIC_SETTINGS {
uint64_t DestCidUpdateIdleTimeoutMs : 1;
uint64_t GreaseQuicBitEnabled : 1;
uint64_t EcnEnabled : 1;
uint64_t RESERVED : 30;
uint64_t HyStartEnabled : 1;
uint64_t StreamRecvWindowBidiLocalDefault : 1;
uint64_t StreamRecvWindowBidiRemoteDefault : 1;
uint64_t StreamRecvWindowUnidiDefault : 1;
#ifdef QUIC_API_ENABLE_PREVIEW_FEATURES
uint64_t EncryptionOffloadAllowed : 1;
uint64_t ReliableResetEnabled : 1;
uint64_t OneWayDelayEnabled : 1;
uint64_t RESERVED : 23;
#else
uint64_t RESERVED : 26;
#endif
} IsSet;
};

Expand Down Expand Up @@ -83,6 +94,23 @@ typedef struct QUIC_SETTINGS {
uint8_t MaxOperationsPerDrain;
uint8_t MtuDiscoveryMissingProbeCount;
uint32_t DestCidUpdateIdleTimeoutMs;
union {
uint64_t Flags;
struct {
uint64_t HyStartEnabled : 1;
#ifdef QUIC_API_ENABLE_PREVIEW_FEATURES
uint64_t EncryptionOffloadAllowed : 1;
uint64_t ReliableResetEnabled : 1;
uint64_t OneWayDelayEnabled : 1;
uint64_t ReservedFlags : 60;
#else
uint64_t ReservedFlags : 63;
#endif
};
};
uint32_t StreamRecvWindowBidiLocalDefault;
uint32_t StreamRecvWindowBidiRemoteDefault;
uint32_t StreamRecvWindowUnidiDefault;

} QUIC_SETTINGS;
```
Expand Down Expand Up @@ -125,9 +153,9 @@ How much server TLS data to buffer. If the application expects very large serve

`StreamRecvWindowDefault`

Initial stream receive window size.
Initial stream receive flow control window size. This applies to all stream types. Limits for specific stream types can be set using `StreamRecvWindowBidirLocalDefault`, `StreamRecvWindowBidirRemoteDefault` and `StreamRecvWindowUnidirDefault`. The value must be a power of 2.

**Default value:** 32,768
**Default value:** 65,536

`StreamRecvBufferDefault`

Expand Down Expand Up @@ -303,6 +331,24 @@ Enable sender-side ECN support. The connection will validate and react to ECN fe

**Default value:** 0 (`FALSE`)

`StreamRecvWindowBidirLocalDefault`

Initial stream receive flow control window size for locally initiated bidirectional streams. If set, this value overwrites the `StreamRecvWindowDefault`.

**Default value:** 0 (no overwrite)

`StreamRecvWindowBidirRemoteDefault`

Initial stream receive flow control window size for remotely initiated bidirectional streams. If set, this value overwrites the `StreamRecvWindowDefault`.

**Default value:** 0 (no overwrite)

`StreamRecvWindowUnidiDefault`

Initial stream receive flow control window size for remotely initiated unidirectional streams. If set, this value overwrites the `StreamRecvWindowDefault`.

**Default value:** 0 (no overwrite)

# Remarks

When setting new values for the settings, the app must set the corresponding `.IsSet.*` parameter for each actual parameter that is being set or updated. For example:
Expand Down
12 changes: 6 additions & 6 deletions src/core/connection.c
Original file line number Diff line number Diff line change
Expand Up @@ -2198,9 +2198,9 @@ QuicConnRecvResumptionTicket(
//
if (ResumedTP.ActiveConnectionIdLimit > QUIC_ACTIVE_CONNECTION_ID_LIMIT ||
ResumedTP.InitialMaxData > Connection->Send.MaxData ||
ResumedTP.InitialMaxStreamDataBidiLocal > Connection->Settings.StreamRecvWindowDefault ||
ResumedTP.InitialMaxStreamDataBidiRemote > Connection->Settings.StreamRecvWindowDefault ||
ResumedTP.InitialMaxStreamDataUni > Connection->Settings.StreamRecvWindowDefault ||
ResumedTP.InitialMaxStreamDataBidiLocal > Connection->Settings.StreamRecvWindowBidiLocalDefault ||
ResumedTP.InitialMaxStreamDataBidiRemote > Connection->Settings.StreamRecvWindowBidiRemoteDefault ||
ResumedTP.InitialMaxStreamDataUni > Connection->Settings.StreamRecvWindowUnidiDefault ||
ResumedTP.InitialMaxUniStreams > Connection->Streams.Types[STREAM_ID_FLAG_IS_CLIENT | STREAM_ID_FLAG_IS_UNI_DIR].MaxTotalStreamCount ||
ResumedTP.InitialMaxBidiStreams > Connection->Streams.Types[STREAM_ID_FLAG_IS_CLIENT | STREAM_ID_FLAG_IS_BI_DIR].MaxTotalStreamCount) {
//
Expand Down Expand Up @@ -2344,9 +2344,9 @@ QuicConnGenerateLocalTransportParameters(
Link);

LocalTP->InitialMaxData = Connection->Send.MaxData;
LocalTP->InitialMaxStreamDataBidiLocal = Connection->Settings.StreamRecvWindowDefault;
LocalTP->InitialMaxStreamDataBidiRemote = Connection->Settings.StreamRecvWindowDefault;
LocalTP->InitialMaxStreamDataUni = Connection->Settings.StreamRecvWindowDefault;
LocalTP->InitialMaxStreamDataBidiLocal = Connection->Settings.StreamRecvWindowBidiLocalDefault;
LocalTP->InitialMaxStreamDataBidiRemote = Connection->Settings.StreamRecvWindowBidiRemoteDefault;
LocalTP->InitialMaxStreamDataUni = Connection->Settings.StreamRecvWindowUnidiDefault;
LocalTP->MaxUdpPayloadSize =
MaxUdpPayloadSizeFromMTU(
CxPlatSocketGetLocalMtu(
Expand Down
3 changes: 3 additions & 0 deletions src/core/quicdef.h
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,9 @@ CXPLAT_STATIC_ASSERT(
#define QUIC_SETTING_MAX_TLS_CLIENT_SEND_BUFFER "TlsClientMaxSendBuffer"
#define QUIC_SETTING_MAX_TLS_SERVER_SEND_BUFFER "TlsServerMaxSendBuffer"
#define QUIC_SETTING_STREAM_FC_WINDOW_SIZE "StreamRecvWindowDefault"
#define QUIC_SETTING_STREAM_FC_BIDI_LOCAL_WINDOW_SIZE "StreamRecvWindowBidiLocalDefault"
#define QUIC_SETTING_STREAM_FC_BIDI_REMOTE_WINDOW_SIZE "StreamRecvWindowBidiRemoteDefault"
#define QUIC_SETTING_STREAM_FC_UNIDI_WINDOW_SIZE "StreamRecvWindowUnidiDefault"
#define QUIC_SETTING_STREAM_RECV_BUFFER_SIZE "StreamRecvBufferDefault"
#define QUIC_SETTING_CONN_FLOW_CONTROL_WINDOW "ConnFlowControlWindow"

Expand Down
133 changes: 132 additions & 1 deletion src/core/settings.c
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,15 @@
if (!Settings->IsSet.StreamRecvWindowDefault) {
Settings->StreamRecvWindowDefault = QUIC_DEFAULT_STREAM_FC_WINDOW_SIZE;
}
if (!Settings->IsSet.StreamRecvWindowBidiLocalDefault) {
Settings->StreamRecvWindowBidiLocalDefault = QUIC_DEFAULT_STREAM_FC_WINDOW_SIZE;
}
if (!Settings->IsSet.StreamRecvWindowBidiRemoteDefault) {
Settings->StreamRecvWindowBidiRemoteDefault = QUIC_DEFAULT_STREAM_FC_WINDOW_SIZE;
}
if (!Settings->IsSet.StreamRecvWindowUnidiDefault) {
Settings->StreamRecvWindowUnidiDefault = QUIC_DEFAULT_STREAM_FC_WINDOW_SIZE;
}
if (!Settings->IsSet.StreamRecvBufferDefault) {
Settings->StreamRecvBufferDefault = QUIC_DEFAULT_STREAM_RECV_BUFFER_SIZE;
}
Expand Down Expand Up @@ -231,6 +240,15 @@
if (!Destination->IsSet.StreamRecvWindowDefault) {
Destination->StreamRecvWindowDefault = Source->StreamRecvWindowDefault;
}
if (!Destination->IsSet.StreamRecvWindowBidiLocalDefault) {
Destination->StreamRecvWindowBidiLocalDefault = Source->StreamRecvWindowBidiLocalDefault;
}
if (!Destination->IsSet.StreamRecvWindowBidiRemoteDefault) {
Destination->StreamRecvWindowBidiRemoteDefault = Source->StreamRecvWindowBidiRemoteDefault;
}
if (!Destination->IsSet.StreamRecvWindowUnidiDefault) {
Destination->StreamRecvWindowUnidiDefault = Source->StreamRecvWindowUnidiDefault;
}
if (!Destination->IsSet.StreamRecvBufferDefault) {
Destination->StreamRecvBufferDefault = Source->StreamRecvBufferDefault;
}
Expand Down Expand Up @@ -496,6 +514,40 @@
}
Destination->StreamRecvWindowDefault = Source->StreamRecvWindowDefault;
Destination->IsSet.StreamRecvWindowDefault = TRUE;

//
// Also set window size for individual stream types, they will be overwritten by a more specific settings if set
//
if (!Destination->IsSet.StreamRecvWindowBidiLocalDefault || OverWrite) {
Destination->StreamRecvWindowBidiLocalDefault = Source->StreamRecvWindowDefault;
}
if (!Destination->IsSet.StreamRecvWindowBidiRemoteDefault || OverWrite) {
Destination->StreamRecvWindowBidiRemoteDefault = Source->StreamRecvWindowDefault;
}
if (!Destination->IsSet.StreamRecvWindowUnidiDefault || OverWrite) {
Destination->StreamRecvWindowUnidiDefault = Source->StreamRecvWindowDefault;
}
}
if (Source->IsSet.StreamRecvWindowBidiLocalDefault && (!Destination->IsSet.StreamRecvWindowBidiLocalDefault || OverWrite)) {
if (Source->StreamRecvWindowBidiLocalDefault == 0 || (Source->StreamRecvWindowBidiLocalDefault & (Source->StreamRecvWindowBidiLocalDefault - 1)) != 0) {
return FALSE; // Must be power of 2

Check warning on line 533 in src/core/settings.c

View check run for this annotation

Codecov / codecov/patch

src/core/settings.c#L533

Added line #L533 was not covered by tests
}
Destination->StreamRecvWindowBidiLocalDefault = Source->StreamRecvWindowBidiLocalDefault;
Destination->IsSet.StreamRecvWindowBidiLocalDefault = TRUE;
}
if (Source->IsSet.StreamRecvWindowBidiRemoteDefault && (!Destination->IsSet.StreamRecvWindowBidiRemoteDefault || OverWrite)) {
if (Source->StreamRecvWindowBidiRemoteDefault == 0 || (Source->StreamRecvWindowBidiRemoteDefault & (Source->StreamRecvWindowBidiRemoteDefault - 1)) != 0) {
return FALSE; // Must be power of 2

Check warning on line 540 in src/core/settings.c

View check run for this annotation

Codecov / codecov/patch

src/core/settings.c#L540

Added line #L540 was not covered by tests
}
Destination->StreamRecvWindowBidiRemoteDefault = Source->StreamRecvWindowBidiRemoteDefault;
Destination->IsSet.StreamRecvWindowBidiRemoteDefault = TRUE;
}
if (Source->IsSet.StreamRecvWindowUnidiDefault && (!Destination->IsSet.StreamRecvWindowUnidiDefault || OverWrite)) {
if (Source->StreamRecvWindowUnidiDefault == 0 || (Source->StreamRecvWindowUnidiDefault & (Source->StreamRecvWindowUnidiDefault - 1)) != 0) {
return FALSE; // Must be power of 2

Check warning on line 547 in src/core/settings.c

View check run for this annotation

Codecov / codecov/patch

src/core/settings.c#L547

Added line #L547 was not covered by tests
}
Destination->StreamRecvWindowUnidiDefault = Source->StreamRecvWindowUnidiDefault;
Destination->IsSet.StreamRecvWindowUnidiDefault = TRUE;
}
if (Source->IsSet.StreamRecvBufferDefault && (!Destination->IsSet.StreamRecvBufferDefault || OverWrite)) {
if (Source->StreamRecvBufferDefault < QUIC_DEFAULT_STREAM_RECV_BUFFER_SIZE) {
Expand Down Expand Up @@ -911,6 +963,33 @@
&ValueLen);
}

if (!Settings->IsSet.StreamRecvWindowBidiLocalDefault) {
ValueLen = sizeof(Settings->StreamRecvWindowBidiLocalDefault);
CxPlatStorageReadValue(
Dismissed Show dismissed Hide dismissed
Storage,
QUIC_SETTING_STREAM_FC_BIDI_LOCAL_WINDOW_SIZE,
(uint8_t*)&Settings->StreamRecvWindowBidiLocalDefault,
&ValueLen);
}

if (!Settings->IsSet.StreamRecvWindowBidiRemoteDefault) {
ValueLen = sizeof(Settings->StreamRecvWindowBidiRemoteDefault);
CxPlatStorageReadValue(
Dismissed Show dismissed Hide dismissed
Storage,
QUIC_SETTING_STREAM_FC_BIDI_REMOTE_WINDOW_SIZE,
(uint8_t*)&Settings->StreamRecvWindowBidiRemoteDefault,
&ValueLen);
}

if (!Settings->IsSet.StreamRecvWindowUnidiDefault) {
ValueLen = sizeof(Settings->StreamRecvWindowUnidiDefault);
CxPlatStorageReadValue(
Dismissed Show dismissed Hide dismissed
Storage,
QUIC_SETTING_STREAM_FC_UNIDI_WINDOW_SIZE,
(uint8_t*)&Settings->StreamRecvWindowUnidiDefault,
&ValueLen);
}

if (!Settings->IsSet.StreamRecvBufferDefault) {
ValueLen = sizeof(Settings->StreamRecvBufferDefault);
CxPlatStorageReadValue(
Expand Down Expand Up @@ -1291,7 +1370,9 @@
QuicTraceLogVerbose(SettingDumpTlsClientMaxSendBuffer, "[sett] TlsClientMaxSendBuffer = %u", Settings->TlsClientMaxSendBuffer);
QuicTraceLogVerbose(SettingDumpTlsServerMaxSendBuffer, "[sett] TlsServerMaxSendBuffer = %u", Settings->TlsServerMaxSendBuffer);
QuicTraceLogVerbose(SettingDumpStreamRecvWindowDefault, "[sett] StreamRecvWindowDefault= %u", Settings->StreamRecvWindowDefault);
QuicTraceLogVerbose(SettingDumpStreamRecvBufferDefault, "[sett] StreamRecvBufferDefault= %u", Settings->StreamRecvBufferDefault);
QuicTraceLogVerbose(SettingDumpStreamRecvWindowBidiLocalDefault, "[sett] StreamRecvWindowBidiLocalDefault = %u", Settings->StreamRecvWindowBidiLocalDefault);
QuicTraceLogVerbose(SettingDumpStreamRecvWindowBidiRemoteDefault, "[sett] StreamRecvWindowBidiRemoteDefault = %u", Settings->StreamRecvWindowBidiRemoteDefault);
QuicTraceLogVerbose(SettingDumpStreamRecvWindowUnidiDefault, "[sett] StreamRecvWindowUnidiDefault = %u", Settings->StreamRecvWindowUnidiDefault);
QuicTraceLogVerbose(SettingDumpConnFlowControlWindow, "[sett] ConnFlowControlWindow = %u", Settings->ConnFlowControlWindow);
QuicTraceLogVerbose(SettingDumpMaxBytesPerKey, "[sett] MaxBytesPerKey = %llu", Settings->MaxBytesPerKey);
QuicTraceLogVerbose(SettingDumpServerResumptionLevel, "[sett] ServerResumptionLevel = %hhu", Settings->ServerResumptionLevel);
Expand Down Expand Up @@ -1402,6 +1483,15 @@
if (Settings->IsSet.StreamRecvWindowDefault) {
QuicTraceLogVerbose(SettingDumpStreamRecvWindowDefault, "[sett] StreamRecvWindowDefault= %u", Settings->StreamRecvWindowDefault);
}
if (Settings->IsSet.StreamRecvWindowBidiLocalDefault) {
QuicTraceLogVerbose(SettingDumpStreamRecvWindowBidiLocalDefault, "[sett] StreamRecvWindowBidiLocalDefault = %u", Settings->StreamRecvWindowBidiLocalDefault);

Check warning on line 1487 in src/core/settings.c

View check run for this annotation

Codecov / codecov/patch

src/core/settings.c#L1487

Added line #L1487 was not covered by tests
}
if (Settings->IsSet.StreamRecvWindowBidiRemoteDefault) {
QuicTraceLogVerbose(SettingDumpStreamRecvWindowBidiRemoteDefault, "[sett] StreamRecvWindowBidiRemoteDefault = %u", Settings->StreamRecvWindowBidiRemoteDefault);

Check warning on line 1490 in src/core/settings.c

View check run for this annotation

Codecov / codecov/patch

src/core/settings.c#L1490

Added line #L1490 was not covered by tests
}
if (Settings->IsSet.StreamRecvWindowUnidiDefault) {
QuicTraceLogVerbose(SettingDumpStreamRecvWindowUnidiDefault, "[sett] StreamRecvWindowUnidiDefault = %u", Settings->StreamRecvWindowUnidiDefault);

Check warning on line 1493 in src/core/settings.c

View check run for this annotation

Codecov / codecov/patch

src/core/settings.c#L1493

Added line #L1493 was not covered by tests
}
if (Settings->IsSet.StreamRecvBufferDefault) {
QuicTraceLogVerbose(SettingDumpStreamRecvBufferDefault, "[sett] StreamRecvBufferDefault= %u", Settings->StreamRecvBufferDefault);
}
Expand Down Expand Up @@ -1700,6 +1790,27 @@
SettingsSize,
InternalSettings);

SETTING_COPY_TO_INTERNAL_SIZED(
StreamRecvWindowBidiLocalDefault,
QUIC_SETTINGS,
Settings,
SettingsSize,
InternalSettings);

SETTING_COPY_TO_INTERNAL_SIZED(
StreamRecvWindowBidiRemoteDefault,
QUIC_SETTINGS,
Settings,
SettingsSize,
InternalSettings);

SETTING_COPY_TO_INTERNAL_SIZED(
StreamRecvWindowUnidiDefault,
QUIC_SETTINGS,
Settings,
SettingsSize,
InternalSettings);

return QUIC_STATUS_SUCCESS;
}

Expand Down Expand Up @@ -1832,6 +1943,26 @@
*SettingsLength,
InternalSettings);

SETTING_COPY_FROM_INTERNAL_SIZED(
StreamRecvWindowBidiLocalDefault,
QUIC_SETTINGS,
Settings,
*SettingsLength,
InternalSettings);

SETTING_COPY_FROM_INTERNAL_SIZED(
StreamRecvWindowBidiRemoteDefault,
QUIC_SETTINGS,
Settings,
*SettingsLength,
InternalSettings);

SETTING_COPY_FROM_INTERNAL_SIZED(StreamRecvWindowUnidiDefault,
QUIC_SETTINGS,
Settings,
*SettingsLength,
InternalSettings);

*SettingsLength = CXPLAT_MIN(*SettingsLength, sizeof(QUIC_SETTINGS));

return QUIC_STATUS_SUCCESS;
Expand Down
8 changes: 7 additions & 1 deletion src/core/settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ typedef struct QUIC_SETTINGS_INTERNAL {
uint64_t TlsClientMaxSendBuffer : 1;
uint64_t TlsServerMaxSendBuffer : 1;
uint64_t StreamRecvWindowDefault : 1;
uint64_t StreamRecvWindowBidiLocalDefault : 1;
uint64_t StreamRecvWindowBidiRemoteDefault : 1;
uint64_t StreamRecvWindowUnidiDefault : 1;
uint64_t StreamRecvBufferDefault : 1;
uint64_t ConnFlowControlWindow : 1;
uint64_t MaxWorkerQueueDelayUs : 1;
Expand Down Expand Up @@ -57,7 +60,7 @@ typedef struct QUIC_SETTINGS_INTERNAL {
uint64_t EncryptionOffloadAllowed : 1;
uint64_t ReliableResetEnabled : 1;
uint64_t OneWayDelayEnabled : 1;
uint64_t RESERVED : 21;
uint64_t RESERVED : 18;
} IsSet;
};

Expand All @@ -69,6 +72,9 @@ typedef struct QUIC_SETTINGS_INTERNAL {
uint32_t TlsClientMaxSendBuffer;
uint32_t TlsServerMaxSendBuffer;
uint32_t StreamRecvWindowDefault;
uint32_t StreamRecvWindowBidiLocalDefault;
uint32_t StreamRecvWindowBidiRemoteDefault;
uint32_t StreamRecvWindowUnidiDefault;
uint32_t StreamRecvBufferDefault;
uint32_t ConnFlowControlWindow;
uint32_t MaxWorkerQueueDelayUs;
Expand Down
8 changes: 7 additions & 1 deletion src/core/stream.c
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,17 @@ QuicStreamInitialize(
}
}

const uint32_t FlowControlWindowSize = Stream->Flags.Unidirectional
? Connection->Settings.StreamRecvWindowUnidiDefault
: OpenedRemotely
? Connection->Settings.StreamRecvWindowBidiRemoteDefault
: Connection->Settings.StreamRecvWindowBidiLocalDefault;

Status =
QuicRecvBufferInitialize(
&Stream->RecvBuffer,
InitialRecvBufferLength,
Connection->Settings.StreamRecvWindowDefault,
FlowControlWindowSize,
FALSE,
PreallocatedRecvBuffer);
if (QUIC_FAILED(Status)) {
Expand Down
Loading
Loading