@@ -137,6 +137,10 @@ void Scheduler::reset()
137
137
threadResults.clear ();
138
138
pushedSnapshotsMap.clear ();
139
139
140
+ // Reset function migration tracking
141
+ inFlightRequests.clear ();
142
+ pendingMigrations.clear ();
143
+
140
144
// Records
141
145
recordedMessagesAll.clear ();
142
146
recordedMessagesLocal.clear ();
@@ -452,6 +456,25 @@ faabric::util::SchedulingDecision Scheduler::doCallFunctions(
452
456
throw std::runtime_error (" Invalid scheduler hint for messages" );
453
457
}
454
458
459
+ // Record in-flight request if function desires to be migrated
460
+ if (firstMsg.migrationcheckperiod () > 0 ) {
461
+ auto decisionPtr =
462
+ std::make_shared<faabric::util::SchedulingDecision>(decision);
463
+ inFlightRequests[decision.appId ] = std::make_pair (req, decisionPtr);
464
+ /*
465
+ if (inFlightRequests.size() == 1) {
466
+ functionMigrationThread.start(firstMsg.migrationcheckperiod());
467
+ } else if (firstMsg.migrationcheckperiod() !=
468
+ functionMigrationThread.wakeUpPeriodSeconds) {
469
+ SPDLOG_WARN("Ignoring migration check period as the migration"
470
+ "thread was initialised with a different one."
471
+ "(provided: {}, current: {})",
472
+ firstMsg.migrationcheckperiod(),
473
+ functionMigrationThread.wakeUpPeriodSeconds);
474
+ }
475
+ */
476
+ }
477
+
455
478
// NOTE: we want to schedule things on this host _last_, otherwise functions
456
479
// may start executing before all messages have been dispatched, thus
457
480
// slowing the remaining scheduling.
@@ -889,6 +912,20 @@ void Scheduler::setFunctionResult(faabric::Message& msg)
889
912
// Write the successful result to the result queue
890
913
std::vector<uint8_t > inputData = faabric::util::messageToBytes (msg);
891
914
redis.publishSchedulerResult (key, msg.statuskey (), inputData);
915
+
916
+ // Remove the app from in-flight map if still there, and this host is the
917
+ // master host for the message
918
+ if (msg.masterhost () == thisHost) {
919
+ faabric::util::FullLock lock (mx);
920
+
921
+ inFlightRequests.erase (msg.appid ());
922
+ pendingMigrations.erase (msg.appid ());
923
+ // If there are no more apps to track, stop the thread checking for
924
+ // migration opportunities
925
+ if (inFlightRequests.size () == 0 ) {
926
+ // functionMigrationThread.stop();
927
+ }
928
+ }
892
929
}
893
930
894
931
void Scheduler::registerThread (uint32_t msgId)
@@ -1130,8 +1167,136 @@ ExecGraphNode Scheduler::getFunctionExecGraphNode(unsigned int messageId)
1130
1167
return node;
1131
1168
}
1132
1169
1133
- void Scheduler::checkForMigrationOpportunities ()
1170
+ void Scheduler::checkForMigrationOpportunities (
1171
+ faabric::util::MigrationStrategy migrationStrategy)
1134
1172
{
1135
- SPDLOG_INFO (" Not implemented" );
1173
+ // Vector to cache all migrations we have to do, and update the shared map
1174
+ // at the very end just once. This is because we need a unique lock to write
1175
+ // to the shared map, but the rest of this method can do with a shared lock.
1176
+ std::vector<std::shared_ptr<faabric::PendingMigrations>>
1177
+ tmpPendingMigrations;
1178
+
1179
+ {
1180
+ faabric::util::SharedLock lock (mx);
1181
+
1182
+ // For each in-flight request, check if there is an opportunity to
1183
+ // migrate
1184
+ for (const auto & app : inFlightRequests) {
1185
+ auto req = app.second .first ;
1186
+ auto originalDecision = *app.second .second ;
1187
+
1188
+ // If we have already recorded a pending migration for this req,
1189
+ // skip
1190
+ if (canAppBeMigrated (originalDecision.appId ) != nullptr ) {
1191
+ continue ;
1192
+ }
1193
+
1194
+ faabric::PendingMigrations msg;
1195
+ msg.set_appid (originalDecision.appId );
1196
+ // TODO - generate a new groupId here for processes to wait on
1197
+ // during the migration? msg.set_groupid();
1198
+
1199
+ if (migrationStrategy ==
1200
+ faabric::util::MigrationStrategy::BIN_PACK) {
1201
+ // We assume the batch was originally scheduled using
1202
+ // bin-packing, thus the scheduling decision has at the begining
1203
+ // (left) the hosts with the most allocated requests, and at the
1204
+ // end (right) the hosts with the fewest. To check for migration
1205
+ // oportunities, we compare a pointer to the possible
1206
+ // destination of the migration (left), with one to the possible
1207
+ // source of the migration (right). NOTE - this is a slight
1208
+ // simplification, but makes the code simpler.
1209
+ auto left = originalDecision.hosts .begin ();
1210
+ auto right = originalDecision.hosts .end () - 1 ;
1211
+ faabric::HostResources r = (*left == thisHost)
1212
+ ? getThisHostResources ()
1213
+ : getHostResources (*left);
1214
+ auto nAvailable = [&r]() -> int {
1215
+ return r.slots () - r.usedslots ();
1216
+ };
1217
+ auto claimSlot = [&r]() {
1218
+ int currentUsedSlots = r.usedslots ();
1219
+ SPDLOG_INFO (" Old slots: {} - New slots: {}" ,
1220
+ currentUsedSlots,
1221
+ currentUsedSlots + 1 );
1222
+ r.set_usedslots (currentUsedSlots + 1 );
1223
+ };
1224
+ while (left < right) {
1225
+ // If both pointers point to the same host, no migration
1226
+ // opportunity, and must check another possible source of
1227
+ // the migration
1228
+ if (*left == *right) {
1229
+ --right;
1230
+ continue ;
1231
+ }
1232
+
1233
+ // If the left pointer (possible destination of the
1234
+ // migration) is out of available resources, no migration
1235
+ // opportunity, and must check another possible destination
1236
+ // of migration
1237
+ if (nAvailable () == 0 ) {
1238
+ auto oldHost = *left;
1239
+ ++left;
1240
+ if (*left != oldHost) {
1241
+ r = (*left == thisHost) ? getThisHostResources ()
1242
+ : getHostResources (*left);
1243
+ }
1244
+ continue ;
1245
+ }
1246
+
1247
+ // If each pointer points to a request scheduled in a
1248
+ // different host, and the possible destination has slots,
1249
+ // there is a migration opportunity
1250
+ auto * migration = msg.add_migrations ();
1251
+ auto msgIdPtr =
1252
+ originalDecision.messageIds .begin () +
1253
+ std::distance (originalDecision.hosts .begin (), right);
1254
+ migration->set_messageid (*msgIdPtr);
1255
+ migration->set_srchost (*right);
1256
+ migration->set_dsthost (*left);
1257
+ // Decrement by one the availability, and check for more
1258
+ // possible sources of migration
1259
+ claimSlot ();
1260
+ --right;
1261
+ }
1262
+ } else {
1263
+ SPDLOG_ERROR (" Unrecognised migration strategy: {}" ,
1264
+ migrationStrategy);
1265
+ throw std::runtime_error (" Unrecognised migration strategy." );
1266
+ }
1267
+
1268
+ if (msg.migrations_size () > 0 ) {
1269
+ tmpPendingMigrations.emplace_back (
1270
+ std::make_shared<faabric::PendingMigrations>(msg));
1271
+ SPDLOG_DEBUG (" Detected migration opportunity for app: {}" ,
1272
+ msg.appid ());
1273
+ } else {
1274
+ SPDLOG_DEBUG (" No migration opportunity detected for app: {}" ,
1275
+ msg.appid ());
1276
+ }
1277
+ }
1278
+ }
1279
+
1280
+ // Finally, store all the pending migrations in the shared map acquiring
1281
+ // a unique lock.
1282
+ if (tmpPendingMigrations.size () > 0 ) {
1283
+ faabric::util::FullLock lock (mx);
1284
+ for (auto msgPtr : tmpPendingMigrations) {
1285
+ SPDLOG_INFO (" Adding app: {}" , msgPtr->appid ());
1286
+ pendingMigrations[msgPtr->appid ()] = std::move (msgPtr);
1287
+ }
1288
+ }
1289
+ }
1290
+
1291
+ std::shared_ptr<faabric::PendingMigrations> Scheduler::canAppBeMigrated (
1292
+ uint32_t appId)
1293
+ {
1294
+ faabric::util::SharedLock lock (mx);
1295
+
1296
+ if (pendingMigrations.find (appId) == pendingMigrations.end ()) {
1297
+ return nullptr ;
1298
+ }
1299
+
1300
+ return pendingMigrations[appId];
1136
1301
}
1137
1302
}
0 commit comments