diff --git a/common/thorhelper/thorsoapcall.cpp b/common/thorhelper/thorsoapcall.cpp index e7ea8283878..05adb49cb12 100644 --- a/common/thorhelper/thorsoapcall.cpp +++ b/common/thorhelper/thorsoapcall.cpp @@ -468,7 +468,7 @@ class BlackLister : public CInterface, implements IThreadFactory { if (e->errorCode() == ROXIE_ABORT_EVENT) throw; - // MCK - do we checkTimeLimitExceeded(&remainingMS) and possibly error out if timelimit exceeded ? + // TODO: do we checkTimeLimitExceeded(&remainingMS) and possibly error out if timelimit exceeded ? if (numAttemptsRemaining > 0) { e->Release(); @@ -1814,27 +1814,16 @@ class CWSCAsyncFor : implements IWSCAsyncFor, public CInterface, public CAsyncFo } else if (curPosn >= currLen) { //nothing in buffer, read from socket - size32_t bytesRead=0; - count = 0; - do - { - socket->readtms(buf + count, 0, len - count, bytesRead, timeoutMS); - count += bytesRead; - } while (count != len); + socket->readtms(buf, len, len, count, timeoutMS); currLen = curPosn = 0; } else { //only some is in buffer, read rest from socket - size32_t bytesRead=0; size32_t avail = currLen - curPosn; memcpy(buf, (buffer + curPosn), avail); count = avail; - do - { - size32_t read; - socket->readtms(buf+avail+bytesRead, 0, len-avail-bytesRead, read, timeoutMS); - bytesRead += read; - } while (len != (bytesRead + avail)); + size32_t bytesRead; + socket->readtms(buf+avail, len-avail, len-avail, bytesRead, timeoutMS); count += bytesRead; currLen = curPosn = 0; } @@ -2096,7 +2085,7 @@ class CWSCAsyncFor : implements IWSCAsyncFor, public CInterface, public CAsyncFo do { checkTimeLimitExceeded(&remainingMS); checkRoxieAbortMonitor(master->roxieAbortMonitor); - socket->readtms(buffer+read, 0, WSCBUFFERSIZE-read, bytesRead, MIN(master->timeoutMS,remainingMS)); + readtmsAllowClose(socket, buffer+read, 1, WSCBUFFERSIZE-read, bytesRead, MIN(master->timeoutMS, remainingMS)); checkTimeLimitExceeded(&remainingMS); checkRoxieAbortMonitor(master->roxieAbortMonitor); @@ -2216,7 +2205,7 @@ class CWSCAsyncFor : implements IWSCAsyncFor, public CInterface, public CAsyncFo while (readroxieAbortMonitor); - socket->readtms(response.reserve(payloadsize-read), 0, payloadsize-read, bytesRead, MIN(master->timeoutMS,remainingMS)); + readtmsAllowClose(socket, response.reserve(payloadsize-read), 1, payloadsize-read, bytesRead, MIN(master->timeoutMS, remainingMS)); checkTimeLimitExceeded(&remainingMS); checkRoxieAbortMonitor(master->roxieAbortMonitor); @@ -2232,7 +2221,7 @@ class CWSCAsyncFor : implements IWSCAsyncFor, public CInterface, public CAsyncFo for (;;) { checkTimeLimitExceeded(&remainingMS); checkRoxieAbortMonitor(master->roxieAbortMonitor); - socket->readtms(buffer, 0, WSCBUFFERSIZE, bytesRead, MIN(master->timeoutMS,remainingMS)); + readtmsAllowClose(socket, buffer, 1, WSCBUFFERSIZE, bytesRead, MIN(master->timeoutMS, remainingMS)); checkTimeLimitExceeded(&remainingMS); checkRoxieAbortMonitor(master->roxieAbortMonitor); @@ -2468,7 +2457,10 @@ class CWSCAsyncFor : implements IWSCAsyncFor, public CInterface, public CAsyncFo { checkTimeLimitExceeded(&remainingMS); Url &connUrl = master->proxyUrlArray.empty() ? url : master->proxyUrlArray.item(0); - ep.set(connUrl.host.get(), connUrl.port); + + // TODO: for DNS, do we use timeoutMS or remainingMS or remainingMS / maxRetries+1 or ? + ep.set(connUrl.host.get(), connUrl.port, master->timeoutMS); + if (ep.isNull()) throw MakeStringException(-1, "Failed to resolve host '%s'", nullText(connUrl.host.get())); @@ -2488,7 +2480,10 @@ class CWSCAsyncFor : implements IWSCAsyncFor, public CInterface, public CAsyncFo { isReused = false; keepAlive = true; + + // TODO: for each connect attempt, do we use timeoutMS or remainingMS or remainingMS / maxRetries or ? socket.setown(blacklist->connect(ep, master->logctx, (unsigned)master->maxRetries, master->timeoutMS, master->roxieAbortMonitor, master->rowProvider)); + if (proto == PersistentProtocol::ProtoTLS) { #ifdef _USE_OPENSSL @@ -2514,7 +2509,6 @@ class CWSCAsyncFor : implements IWSCAsyncFor, public CInterface, public CAsyncFo err.append(": OpenSSL disabled in build"); throw makeStringException(0, err.str()); #endif - } } break; diff --git a/common/workunit/workunit.cpp b/common/workunit/workunit.cpp index 7fb51f419df..ba694168327 100644 --- a/common/workunit/workunit.cpp +++ b/common/workunit/workunit.cpp @@ -1494,7 +1494,14 @@ class GraphScopeIterator : public CInterfaceOf { if (whichProperties & PTattributes) { - playAttribute(visitor, WaLabel); + Owned nodeAttrs = cur.getAttributes(); + ForEach(*nodeAttrs) + { + const char * name = nodeAttrs->queryName(); + WuAttr attr = queryGraphAttrToWuAttr(name+1); + if (attr != WaNone) + visitor.noteAttribute(attr, nodeAttrs->queryValue()); + } Owned attrs = cur.getElements("att"); ForEach(*attrs) { @@ -1515,7 +1522,6 @@ class GraphScopeIterator : public CInterfaceOf } if (whichProperties & PTstatistics) { - playAttribute(visitor, WaLabel); Owned attrs = cur.getElements("att"); ForEach(*attrs) { diff --git a/common/workunit/wuattr.cpp b/common/workunit/wuattr.cpp index d9419520af2..a174337d2b8 100644 --- a/common/workunit/wuattr.cpp +++ b/common/workunit/wuattr.cpp @@ -151,6 +151,7 @@ const static WuAttrInfo attrInfo[] = { CHILD(RecordFormat, SMeasureText, "recordFormat"), CHILD(ServiceName, SMeasureText, "serviceName"), CHILD(SizeComplexHelper, SMeasureSize, "complexHelperSize"), + CHILD(IsBalanced, SMeasureBool, "balanced"), { WaMax, SMeasureNone, nullptr, nullptr, nullptr, nullptr, nullptr, WaNone, WaNone } }; diff --git a/common/workunit/wuattr.hpp b/common/workunit/wuattr.hpp index 7dc1d098768..6e7639293b6 100644 --- a/common/workunit/wuattr.hpp +++ b/common/workunit/wuattr.hpp @@ -136,6 +136,7 @@ enum WuAttr : unsigned WaRecordFormat, WaServiceName, WaSizeComplexHelper, + WaIsBalanced, WaMax }; inline WuAttr & operator++(WuAttr & x) { assert(x, ds) with PROJECT"); + if (doTrace(traceOptimizations)) + DBGLOG("Folder: Replace JOIN(, ds) with PROJECT"); return ret.getClear(); } break; @@ -4761,7 +4764,8 @@ IHqlExpression * NullFolderMixin::foldNullDataset(IHqlExpression * expr) case 1: if (op == no_cogroup) { - DBGLOG("Folder: Replace %s with group", getOpString(op)); + if (doTrace(traceOptimizations)) + DBGLOG("Folder: Replace %s with group", getOpString(op)); IHqlExpression * grouping = queryAttributeChild(expr, groupAtom, 0); IHqlExpression * mappedGrouping = replaceSelector(grouping, queryActiveTableSelector(), lastInput); OwnedHqlExpr group = createDataset(no_group, LINK(lastInput), mappedGrouping); @@ -4769,11 +4773,13 @@ IHqlExpression * NullFolderMixin::foldNullDataset(IHqlExpression * expr) } else { - DBGLOG("Folder: Replace %s with child", getOpString(op)); + if (doTrace(traceOptimizations)) + DBGLOG("Folder: Replace %s with child", getOpString(op)); return LINK(lastInput); } default: - DBGLOG("Folder: Remove %d inputs from %s", expr->numChildren()-args.ordinality(), getOpString(op)); + if (doTrace(traceOptimizations)) + DBGLOG("Folder: Remove %d inputs from %s", expr->numChildren()-args.ordinality(), getOpString(op)); return expr->clone(args); } } @@ -5632,9 +5638,12 @@ IHqlExpression * CExprFolderTransformer::doFoldTransformed(IHqlExpression * unfo if (expandedFilter->isConstant()) { //Following would be sensible, but can't call transform at this point, so replace arg, and wait for it to re-iterate - IIdAtom * nameF = expr->queryId(); - IIdAtom * nameP = child->queryId(); - DBGLOG("Folder: Combining FILTER %s with %s %s produces constant filter", nameF ? str(nameF) : "", getOpString(child->getOperator()), nameP ? str(nameP) : ""); + if (doTrace(traceOptimizations)) + { + IIdAtom * nameF = expr->queryId(); + IIdAtom * nameP = child->queryId(); + DBGLOG("Folder: Combining FILTER %s with %s %s produces constant filter", nameF ? str(nameF) : "", getOpString(child->getOperator()), nameP ? str(nameP) : ""); + } expandedFilter.setown(transformExpanded(expandedFilter)); IValue * value = expandedFilter->queryValue(); if (value) @@ -5685,8 +5694,11 @@ IHqlExpression * CExprFolderTransformer::doFoldTransformed(IHqlExpression * unfo return replaceWithNull(expr); if (filtered.ordinality() == values->numChildren()) return removeParentNode(expr); - StringBuffer s1, s2; - DBGLOG("Folder: Node %s reduce values in child: %s from %d to %d", queryChildNodeTraceText(s1, expr), queryChildNodeTraceText(s2, child), values->numChildren(), filtered.ordinality()); + if (doTrace(traceOptimizations)) + { + StringBuffer s1, s2; + DBGLOG("Folder: Node %s reduce values in child: %s from %d to %d", queryChildNodeTraceText(s1, expr), queryChildNodeTraceText(s2, child), values->numChildren(), filtered.ordinality()); + } HqlExprArray args; args.append(*values->clone(filtered)); unwindChildren(args, child, 1); @@ -6587,7 +6599,10 @@ IHqlExpression * CExprFolderTransformer::createTransformed(IHqlExpression * expr #ifdef LOG_ALL_FOLDING if ((op != transformed->getOperator()) || (expr->numChildren() != transformed->numChildren())) - DBGLOG("Folding %s to %s", getOpString(updated->getOperator()), getOpString(transformed->getOperator())); + { + if (doTrace(traceOptimizations)) + DBGLOG("Folding %s to %s", getOpString(updated->getOperator()), getOpString(transformed->getOperator())); + } #endif #ifdef _DEBUG @@ -7183,21 +7198,24 @@ IHqlExpression * CExprFolderTransformer::percolateConstants(IHqlExpression * exp IHqlExpression * CExprFolderTransformer::removeParentNode(IHqlExpression * expr) { IHqlExpression * child = expr->queryChild(0); - DBGLOG("Folder: Node %s remove self (now %s)", queryNode0Text(expr), queryNode1Text(child)); + if (doTrace(traceOptimizations)) + DBGLOG("Folder: Node %s remove self (now %s)", queryNode0Text(expr), queryNode1Text(child)); return LINK(child); } IHqlExpression * CExprFolderTransformer::replaceWithNull(IHqlExpression * expr) { IHqlExpression * ret = createNullExpr(expr); - DBGLOG("Folder: Replace %s with %s", queryNode0Text(expr), queryNode1Text(ret)); + if (doTrace(traceOptimizations)) + DBGLOG("Folder: Replace %s with %s", queryNode0Text(expr), queryNode1Text(ret)); return ret; } IHqlExpression * CExprFolderTransformer::replaceWithNullRow(IHqlExpression * expr) { IHqlExpression * ret = createRow(no_null, LINK(expr->queryRecord())); - DBGLOG("Folder: Replace %s with %s", queryNode0Text(expr), queryNode1Text(ret)); + if (doTrace(traceOptimizations)) + DBGLOG("Folder: Replace %s with %s", queryNode0Text(expr), queryNode1Text(ret)); return ret; } diff --git a/ecl/hql/hqlopt.cpp b/ecl/hql/hqlopt.cpp index 53786c35d1e..e0bfbd2bfe6 100644 --- a/ecl/hql/hqlopt.cpp +++ b/ecl/hql/hqlopt.cpp @@ -269,7 +269,8 @@ inline IHqlExpression * makeChildList(IHqlExpression * expr) IHqlExpression * CTreeOptimizer::removeChildNode(IHqlExpression * expr) { IHqlExpression * child = expr->queryChild(0); - DBGLOG("Optimizer: Node %s remove child: %s", queryNode0Text(expr), queryNode1Text(child)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Node %s remove child: %s", queryNode0Text(expr), queryNode1Text(child)); noteUnused(child); //If removing an operator that also has the side-effect of removing grouping (e.g. distribute), make sure the dataset is still ungrouped @@ -285,7 +286,8 @@ IHqlExpression * CTreeOptimizer::removeChildNode(IHqlExpression * expr) IHqlExpression * CTreeOptimizer::removeParentNode(IHqlExpression * expr) { IHqlExpression * child = expr->queryChild(0); - DBGLOG("Optimizer: Node %s remove self (now %s)", queryNode0Text(expr), queryNode1Text(child)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Node %s remove self (now %s)", queryNode0Text(expr), queryNode1Text(child)); // Need to dec link count of child because it is just about to inherited the link count from the parent decUsage(child); @@ -295,7 +297,8 @@ IHqlExpression * CTreeOptimizer::removeParentNode(IHqlExpression * expr) IHqlExpression * CTreeOptimizer::swapNodeWithChild(IHqlExpression * parent) { IHqlExpression * child = parent->queryChild(0); - DBGLOG("Optimizer: Swap %s and %s", queryNode0Text(parent), queryNode1Text(child)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Swap %s and %s", queryNode0Text(parent), queryNode1Text(child)); OwnedHqlExpr newParent = swapDatasets(parent); //if this is the only reference to the child (almost certainly true) then no longer refd, so don't inc usage for child. noteUnused(child); @@ -314,7 +317,8 @@ IHqlExpression * CTreeOptimizer::forceSwapNodeWithChild(IHqlExpression * parent) IHqlExpression * CTreeOptimizer::swapNodeWithChild(IHqlExpression * parent, unsigned childIndex) { IHqlExpression * child = parent->queryChild(0); - DBGLOG("Optimizer: Swap %s and %s", queryNode0Text(parent), queryNode1Text(child)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Swap %s and %s", queryNode0Text(parent), queryNode1Text(child)); OwnedHqlExpr newChild = replaceChildDataset(parent, child->queryChild(childIndex), 0); OwnedHqlExpr swapped = insertChildDataset(child, newChild, childIndex); if (!alreadyHasUsage(swapped)) @@ -364,7 +368,8 @@ IHqlExpression * CTreeOptimizer::swapIntoIf(IHqlExpression * expr, bool force) decUsage(newRight); } noteUnused(child); - DBGLOG("Optimizer: Swap %s and %s", queryNode0Text(expr), queryNode1Text(child)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Swap %s and %s", queryNode0Text(expr), queryNode1Text(child)); return transformedIf.getClear(); } @@ -420,7 +425,8 @@ IHqlExpression * CTreeOptimizer::swapIntoAddFiles(IHqlExpression * expr, bool fo noteUnused(child); //And create the new funnel - DBGLOG("Optimizer: Swap %s and %s", queryNode0Text(expr), queryNode1Text(child)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Swap %s and %s", queryNode0Text(expr), queryNode1Text(child)); return child->clone(transformedArgs); } @@ -463,7 +469,8 @@ IHqlExpression * CTreeOptimizer::moveFilterOverSelect(IHqlExpression * expr) if (hoisted.ordinality() == 0) return NULL; - DBGLOG("Optimizer: Move filter over select (%d/%d)", hoisted.ordinality(), args.ordinality()); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Move filter over select (%d/%d)", hoisted.ordinality(), args.ordinality()); //Create a filtered dataset IHqlExpression * inDs = LINK(ds); @@ -742,7 +749,8 @@ IHqlExpression * CTreeOptimizer::optimizeAggregateDataset(IHqlExpression * trans incUsage(ds); } - DBGLOG("Optimizer: Aggregate replace %s with %s", queryNode0Text(root), queryNode1Text(ds)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Aggregate replace %s with %s", queryNode0Text(root), queryNode1Text(ds)); children.replace(*ds.getClear(), 0); return transformed->clone(children); } @@ -794,7 +802,8 @@ IHqlExpression * CTreeOptimizer::optimizeDatasetIf(IHqlExpression * transformed) OwnedHqlExpr ret = createDataset(no_filter, args); - DBGLOG("Optimizer: Convert %s to a filter", queryNode0Text(transformed)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Convert %s to a filter", queryNode0Text(transformed)); //NOTE: left and right never walk over any shared nodes, so don't need to decrement usage for //child(1), child(2) or intermediate nodes to left/right, since not referenced any more. @@ -869,7 +878,8 @@ IHqlExpression * CTreeOptimizer::optimizeIfAppend(IHqlExpression * expr, node_op args.append(*newIf.getClear()); unwindChildren(args, appendExpr, 2); OwnedHqlExpr ret = appendExpr->clone(args); - DBGLOG("Optimizer: Extract common branch - replace %s with %s", queryNode0Text(expr), queryNode1Text(ret)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Extract common branch - replace %s with %s", queryNode0Text(expr), queryNode1Text(ret)); return ret.getClear(); } @@ -1045,7 +1055,8 @@ IHqlExpression * CTreeOptimizer::optimizeIf(IHqlExpression * expr) if (args.ordinality()) { - DBGLOG("Optimizer: Merge %s and %s", queryNode0Text(expr), queryNode1Text(trueExpr)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Merge %s and %s", queryNode0Text(expr), queryNode1Text(trueExpr)); noteUnused(falseExpr); } } @@ -1072,7 +1083,8 @@ IHqlExpression * CTreeOptimizer::optimizeIf(IHqlExpression * expr) if (args.ordinality()) { - DBGLOG("Optimizer: Merge %s and %s", queryNode0Text(expr), queryNode1Text(falseExpr)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Merge %s and %s", queryNode0Text(expr), queryNode1Text(falseExpr)); noteUnused(trueExpr); } } @@ -1122,7 +1134,7 @@ bool CTreeOptimizer::expandFilterCondition(HqlExprArray & expanded, HqlExprArray IValue * value = expandedFilter->queryValue(); if (value && !value->getBoolValue()) { - if (onlyKeyed) + if (onlyKeyed && doTrace(traceOptimizations)) DBGLOG("Optimizer: Merging filter over shared project always false"); expanded.kill(); expanded.append(*LINK(expandedFilter)); @@ -1197,9 +1209,15 @@ IHqlExpression * CTreeOptimizer::hoistFilterOverProject(IHqlExpression * transfo OwnedHqlExpr filterExpr = createFilterCondition(expanded); if (unexpanded.ordinality()) - DBGLOG("Optimizer: Move %d/%d filters over %s", expanded.ordinality(), expanded.ordinality()+unexpanded.ordinality(), queryNode1Text(child)); + { + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Move %d/%d filters over %s", expanded.ordinality(), expanded.ordinality()+unexpanded.ordinality(), queryNode1Text(child)); + } else - DBGLOG("Optimizer: Swap %s and %s", queryNode0Text(transformed), queryNode1Text(child)); + { + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Swap %s and %s", queryNode0Text(transformed), queryNode1Text(child)); + } IHqlExpression * newGrandchild = child->queryChild(0); OwnedHqlExpr newFilter = createDataset(no_filter, LINK(newGrandchild), LINK(filterExpr)); @@ -1331,7 +1349,8 @@ IHqlExpression * CTreeOptimizer::getHoistedFilter(IHqlExpression * transformed, //extend the join condition where appropriate if (expanded.ordinality()) { - DBGLOG("Optimizer: Merge filters(%d/%d) into %s condition", expanded.ordinality(), conds.ordinality(), queryNode1Text(child)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Merge filters(%d/%d) into %s condition", expanded.ordinality(), conds.ordinality(), queryNode1Text(child)); OwnedITypeInfo boolType = makeBoolType(); HqlExprArray args; unwindChildren(args, ret); @@ -1366,7 +1385,8 @@ IHqlExpression * CTreeOptimizer::getHoistedFilter(IHqlExpression * transformed, IHqlExpression * CTreeOptimizer::createHoistedFilter(IHqlExpression * expr, HqlExprArray & conditions, unsigned childIndex, unsigned maxConditions) { IHqlExpression * grand = expr->queryChild(childIndex); - DBGLOG("Optimizer: Hoisting filter(%d/%d) over %s.%d", conditions.ordinality(), maxConditions, queryNode0Text(expr), childIndex); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Hoisting filter(%d/%d) over %s.%d", conditions.ordinality(), maxConditions, queryNode0Text(expr), childIndex); conditions.add(*LINK(grand), 0); OwnedHqlExpr hoistedFilter = createDataset(no_filter, conditions); OwnedHqlExpr ret = insertChildDataset(expr, hoistedFilter, childIndex); @@ -1401,7 +1421,8 @@ IHqlExpression * CTreeOptimizer::queryPromotedFilter(IHqlExpression * expr, node if (hoisted.ordinality() == 0) return NULL; - DBGLOG("Optimizer: Hoisting filter(%d/%d) over %s", hoisted.ordinality(), hoisted.ordinality()+unhoisted.ordinality(), queryNode0Text(child)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Hoisting filter(%d/%d) over %s", hoisted.ordinality(), hoisted.ordinality()+unhoisted.ordinality(), queryNode0Text(child)); OwnedHqlExpr newChild = createHoistedFilter(child, hoisted, childIndex, conds.ordinality()); noteUnused(child); @@ -1569,7 +1590,8 @@ IHqlExpression * CTreeOptimizer::optimizeJoinCondition(IHqlExpression * expr) if (leftOnly.ordinality()) { - DBGLOG("Optimizer: Hoist %d LEFT conditions out of %s", leftOnly.ordinality(), queryNode0Text(expr)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Hoist %d LEFT conditions out of %s", leftOnly.ordinality(), queryNode0Text(expr)); IHqlExpression * lhs = expr->queryChild(0); OwnedHqlExpr left = createSelector(no_left, lhs, seq); OwnedHqlExpr leftFilter = createFilterCondition(leftOnly); @@ -1580,7 +1602,8 @@ IHqlExpression * CTreeOptimizer::optimizeJoinCondition(IHqlExpression * expr) if (rightOnly.ordinality()) { - DBGLOG("Optimizer: Hoist %d RIGHT conditions out of %s", rightOnly.ordinality(), queryNode0Text(expr)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Hoist %d RIGHT conditions out of %s", rightOnly.ordinality(), queryNode0Text(expr)); IHqlExpression * rhs = expr->queryChild(1); OwnedHqlExpr right = createSelector(no_right, rhs, seq); OwnedHqlExpr rightFilter = createFilterCondition(rightOnly); @@ -1617,7 +1640,8 @@ IHqlExpression * CTreeOptimizer::optimizeDistributeDedup(IHqlExpression * expr) if (!matchDedupDistribution(dist, info.equalities)) return NULL; - DBGLOG("Optimizer: Swap %s and %s", queryNode0Text(expr), queryNode1Text(child)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Swap %s and %s", queryNode0Text(expr), queryNode1Text(child)); OwnedHqlExpr distn; @@ -1717,7 +1741,8 @@ IHqlExpression * CTreeOptimizer::optimizeProjectInlineTable(IHqlExpression * tra newValues.append(*ensureTransformType(next, no_transform)); } - DBGLOG("Optimizer: Merge %s and %s", queryNode0Text(transformed), queryNode1Text(child)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Merge %s and %s", queryNode0Text(transformed), queryNode1Text(child)); HqlExprArray args; args.append(*createValue(no_transformlist, makeNullType(), newValues)); if (projectOp == no_newusertable) @@ -1848,7 +1873,8 @@ IHqlExpression * CTreeOptimizer::expandProjectedDataset(IHqlExpression * child, IHqlExpression * oldTransform = child->queryChild(transformIndex); expandedTransform.setown(ensureTransformType(expandedTransform, oldTransform->getOperator())); - DBGLOG("Optimizer: Merge %s and %s", queryNode0Text(expr), queryNode1Text(child)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Merge %s and %s", queryNode0Text(expr), queryNode1Text(child)); HqlExprArray args; unwindChildren(args, child); args.replace(*expandedTransform.getClear(), transformIndex); @@ -2093,7 +2119,8 @@ IHqlExpression * CTreeOptimizer::moveProjectionOverSimple(IHqlExpression * trans } } - DBGLOG("Optimizer: Swap %s and %s", queryNode0Text(transformed), queryNode1Text(child)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Swap %s and %s", queryNode0Text(transformed), queryNode1Text(child)); OwnedHqlExpr swapped = child->clone(args); if (!alreadyHasUsage(swapped)) incUsage(newProject); @@ -2135,7 +2162,8 @@ IHqlExpression * CTreeOptimizer::moveProjectionOverLimit(IHqlExpression * transf if (monitor.isComplex()) return LINK(transformed); - DBGLOG("Optimizer: Swap %s and %s", queryNode0Text(transformed), queryNode1Text(child)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Swap %s and %s", queryNode0Text(transformed), queryNode1Text(child)); OwnedHqlExpr swapped = child->clone(args); if (!alreadyHasUsage(swapped)) incUsage(newProject); @@ -2249,7 +2277,8 @@ IHqlExpression * CTreeOptimizer::getOptimizedFilter(IHqlExpression * transformed noteUnused(transformed->queryChild(0)); //MORE: Really wants to walk down the entire chain until we hit something that is shared. IHqlExpression * ret = createNullDataset(transformed); - DBGLOG("Optimizer: Replace %s with %s", queryNode0Text(transformed), queryNode1Text(ret)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Replace %s with %s", queryNode0Text(transformed), queryNode1Text(ret)); return ret; } } @@ -2310,7 +2339,8 @@ void CTreeOptimizer::recursiveDecChildUsage(IHqlExpression * expr) IHqlExpression * CTreeOptimizer::replaceWithNull(IHqlExpression * transformed) { IHqlExpression * ret = createNullExpr(transformed); - DBGLOG("Optimizer: Replace %s with %s", queryNode0Text(transformed), queryNode1Text(ret)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Replace %s with %s", queryNode0Text(transformed), queryNode1Text(ret)); recursiveDecChildUsage(transformed); return ret; @@ -2320,7 +2350,8 @@ IHqlExpression * CTreeOptimizer::replaceWithNull(IHqlExpression * transformed) IHqlExpression * CTreeOptimizer::replaceWithNullRow(IHqlExpression * expr) { IHqlExpression * ret = createRow(no_null, LINK(expr->queryRecord())); - DBGLOG("Optimizer: Replace %s with %s", queryNode0Text(expr), queryNode1Text(ret)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Replace %s with %s", queryNode0Text(expr), queryNode1Text(ret)); recursiveDecChildUsage(expr); return ret; @@ -2329,7 +2360,8 @@ IHqlExpression * CTreeOptimizer::replaceWithNullRowDs(IHqlExpression * expr) { assertex(!isGrouped(expr)); IHqlExpression * ret = createDatasetFromRow(createRow(no_null, LINK(expr->queryRecord()))); - DBGLOG("Optimizer: Replace %s with %s", queryNode0Text(expr), queryNode1Text(ret)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Replace %s with %s", queryNode0Text(expr), queryNode1Text(ret)); recursiveDecChildUsage(expr); return ret; @@ -2452,7 +2484,8 @@ IHqlExpression * CTreeOptimizer::doCreateTransformed(IHqlExpression * transforme IHqlExpression * ret = optimizeCreateRow(transformed); if (ret) { - DBGLOG("Optimizer: Remove Redundant %s", queryNode0Text(transformed)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Remove Redundant %s", queryNode0Text(transformed)); if (ret->isDataset()) return ensureActiveRow(ret); return LINK(ret); @@ -2529,7 +2562,8 @@ IHqlExpression * CTreeOptimizer::doCreateTransformed(IHqlExpression * transforme IHqlExpression * rhs = transformed->queryChild(1); if (rhs->getOperator() == no_distribute) { - DBGLOG("Optimizer: Remove %s from RHS of global LOOKUP JOIN", queryNode0Text(rhs)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Remove %s from RHS of global LOOKUP JOIN", queryNode0Text(rhs)); return ::replaceChild(transformed, 1, rhs->queryChild(0)); } } @@ -2638,7 +2672,8 @@ IHqlExpression * CTreeOptimizer::doCreateTransformed(IHqlExpression * transforme //MORE If trivial projection then might be worth merging with multiple items, but unlikely to occur in practice OwnedHqlExpr ret = createRow(no_createrow, LINK(values->queryChild((unsigned)index-1))); noteUnused(child); - DBGLOG("Optimizer: Replace %s with %s", queryNode0Text(transformed), queryNode1Text(ret)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Replace %s with %s", queryNode0Text(transformed), queryNode1Text(ret)); return ret.getClear(); } case no_datasetfromrow: @@ -2653,7 +2688,8 @@ IHqlExpression * CTreeOptimizer::doCreateTransformed(IHqlExpression * transforme IHqlExpression * ret = child->queryChild(0); noteUnused(child); decUsage(ret); // will inherit later - DBGLOG("Optimizer: Replace %s with %s", queryNode0Text(transformed), queryNode1Text(ret)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Replace %s with %s", queryNode0Text(transformed), queryNode1Text(ret)); return LINK(ret); } #if 0 @@ -2693,7 +2729,8 @@ IHqlExpression * CTreeOptimizer::doCreateTransformed(IHqlExpression * transforme //being used conditionally within transforms. See HPCC-11018 for details. if ((options & HOOexpandselectcreaterow) || isIndependentOfScope(match)) { - DBGLOG("Optimizer: Extract value %s from %s", queryNode0Text(cur), queryNode1Text(transformed)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Extract value %s from %s", queryNode0Text(cur), queryNode1Text(transformed)); noteUnused(child); return match.getClear(); } @@ -2705,7 +2742,8 @@ IHqlExpression * CTreeOptimizer::doCreateTransformed(IHqlExpression * transforme case no_null: case no_getresult: case no_getgraphresult: - DBGLOG("Optimizer: Extract value %s from %s", queryNode0Text(match), queryNode1Text(transformed)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Extract value %s from %s", queryNode0Text(match), queryNode1Text(transformed)); noteUnused(child); return match.getClear(); } @@ -2745,7 +2783,8 @@ IHqlExpression * CTreeOptimizer::doCreateTransformed(IHqlExpression * transforme case no_left: case no_right: { - DBGLOG("Optimizer: Extract value %s from %s", queryNode0Text(match), queryNode1Text(transformed)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Extract value %s from %s", queryNode0Text(match), queryNode1Text(transformed)); noteUnused(child); return match.getClear(); } @@ -2788,7 +2827,8 @@ IHqlExpression * CTreeOptimizer::doCreateTransformed(IHqlExpression * transforme case no_getresult: case no_getgraphresult: { - DBGLOG("Optimizer: Extract value %s from %s", queryNode0Text(match), queryNode1Text(transformed)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Extract value %s from %s", queryNode0Text(match), queryNode1Text(transformed)); noteUnused(child); HqlExprArray args; @@ -2816,7 +2856,8 @@ IHqlExpression * CTreeOptimizer::doCreateTransformed(IHqlExpression * transforme { assertex(isGrouped(child)); // not grouped handled already. OwnedHqlExpr ret = createDataset(no_group, LINK(child)); - DBGLOG("Optimizer: replace %s with %s", queryNode0Text(transformed), queryNode1Text(ret)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: replace %s with %s", queryNode0Text(transformed), queryNode1Text(ret)); return transformed->cloneAllAnnotations(ret); } break; @@ -2890,7 +2931,8 @@ IHqlExpression * CTreeOptimizer::doCreateTransformed(IHqlExpression * transforme else args.append(*LINK(transform)); - DBGLOG("Optimizer: Convert %s(,1) into PROJECT", queryNode0Text(transformed)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Convert %s(,1) into PROJECT", queryNode0Text(transformed)); unwindChildren(args, transformed, 3); //This is not a count project.. so remove the attribute. removeAttribute(args, _countProject_Atom); @@ -3020,7 +3062,8 @@ IHqlExpression * CTreeOptimizer::doCreateTransformed(IHqlExpression * transforme else newLimit = const2; - DBGLOG("Optimizer: Merge %s and %s", queryNode0Text(transformed), queryNode1Text(child)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Merge %s and %s", queryNode0Text(transformed), queryNode1Text(child)); return createDataset(no_choosen, LINK(child->queryChild(0)), LINK(newLimit)); //don't bother to transform } @@ -3188,7 +3231,8 @@ IHqlExpression * CTreeOptimizer::doCreateTransformed(IHqlExpression * transforme { case no_filter: { - DBGLOG("Optimizer: Merge %s and %s", queryNode0Text(transformed), queryNode1Text(child)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Merge %s and %s", queryNode0Text(transformed), queryNode1Text(child)); HqlExprArray args; unwindChildren(args, child); unwindChildren(args, transformed, 1); @@ -3395,7 +3439,8 @@ IHqlExpression * CTreeOptimizer::doCreateTransformed(IHqlExpression * transforme if (filtered.ordinality() == values->numChildren()) return removeParentNode(transformed); - DBGLOG("Optimizer: Node %s reduce values in child: %s from %d to %d", queryNode0Text(transformed), queryNode1Text(child), values->numChildren(), filtered.ordinality()); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Node %s reduce values in child: %s from %d to %d", queryNode0Text(transformed), queryNode1Text(child), values->numChildren(), filtered.ordinality()); HqlExprArray args; args.append(*values->clone(filtered)); unwindChildren(args, child, 1); @@ -3493,7 +3538,8 @@ IHqlExpression * CTreeOptimizer::doCreateTransformed(IHqlExpression * transforme if (expandedTransform && !monitor.isComplex()) { expandedTransform.setown(inheritSkips(expandedTransform, child->queryChild(1), mapper->queryTransformSelector(), newLeft)); - DBGLOG("Optimizer: Merge %s and %s", queryNode0Text(transformed), queryNode1Text(child)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Merge %s and %s", queryNode0Text(transformed), queryNode1Text(child)); //NB: Merging a project with a count project can actually remove the count project.. IHqlExpression * countProjectAttr = transformedCountProject; if (childCountProject && transformContainsCounter(expandedTransform, childCountProject->queryChild(0))) @@ -3632,7 +3678,8 @@ IHqlExpression * CTreeOptimizer::doCreateTransformed(IHqlExpression * transforme OwnedHqlExpr expandedTransform = expandFields(mapper, transform, oldLeft, NULL, &monitor); if (expandedTransform && !monitor.isComplex()) { - DBGLOG("Optimizer: Merge %s and %s", queryNode0Text(transformed), queryNode1Text(child)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Merge %s and %s", queryNode0Text(transformed), queryNode1Text(child)); HqlExprArray args; unwindChildren(args, child); args.replace(*expandedTransform.getClear(), queryTransformIndex(child)); @@ -3748,7 +3795,8 @@ IHqlExpression * CTreeOptimizer::doCreateTransformed(IHqlExpression * transforme if (!monitor.isComplex()) { - DBGLOG("Optimizer: Merge %s and %s", queryNode0Text(transformed), queryNode1Text(child)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Merge %s and %s", queryNode0Text(transformed), queryNode1Text(child)); removeAttribute(args, _internal_Atom); noteUnused(child); return exprToClone->clone(args); @@ -3933,7 +3981,8 @@ IHqlExpression * CTreeOptimizer::doCreateTransformed(IHqlExpression * transforme break; OwnedHqlExpr newOrder = replaceSelector(sortOrder, queryActiveTableSelector(), child->queryNormalizedSelector()); decUsage(child); - DBGLOG("Optimizer: Merge %s and %s", queryNode0Text(transformed), queryNode1Text(child)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Merge %s and %s", queryNode0Text(transformed), queryNode1Text(child)); return ::replaceChild(child, 1, newOrder); } @@ -4088,7 +4137,8 @@ IHqlExpression * CTreeOptimizer::doCreateTransformed(IHqlExpression * transforme IHqlExpression * expanded = expandFields(mapper, transformed->queryChild(3), oldLeft, newLeft); if (expanded) { - DBGLOG("Optimizer: Merge %s and %s", queryNode0Text(transformed), queryNode1Text(child)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Merge %s and %s", queryNode0Text(transformed), queryNode1Text(child)); HqlExprArray args; args.append(*LINK(child->queryChild(0))); args.append(*LINK(transformed->queryChild(1))); @@ -4122,7 +4172,8 @@ IHqlExpression * CTreeOptimizer::doCreateTransformed(IHqlExpression * transforme } if (!ok) break; - DBGLOG("Optimizer: Merge inline tables for %s", queryNode0Text(transformed)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Merge inline tables for %s", queryNode0Text(transformed)); HqlExprArray args; args.append(*createValue(no_transformlist, makeNullType(), allTransforms)); args.append(*LINK(child->queryRecord())); @@ -4146,7 +4197,8 @@ IHqlExpression * CTreeOptimizer::doCreateTransformed(IHqlExpression * transforme OwnedHqlExpr ret = transformTrivialSelectProject(transformed); if (ret) { - DBGLOG("Optimizer: Select %s from %s optimized", ret->queryChild(1)->queryName()->str(), queryNode1Text(child)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Select %s from %s optimized", ret->queryChild(1)->queryName()->str(), queryNode1Text(child)); noteUnused(child); return ret.getClear(); } @@ -4161,7 +4213,8 @@ IHqlExpression * CTreeOptimizer::doCreateTransformed(IHqlExpression * transforme { case no_createrow: { - DBGLOG("Optimizer: Merge %s and %s to Inline table", queryNode0Text(transformed), queryNode1Text(child)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Merge %s and %s to Inline table", queryNode0Text(transformed), queryNode1Text(child)); HqlExprArray args; args.append(*createValue(no_transformlist, makeNullType(), LINK(child->queryChild(0)))); args.append(*LINK(child->queryRecord())); @@ -4219,7 +4272,8 @@ IHqlExpression * CTreeOptimizer::doCreateTransformed(IHqlExpression * transforme //If expanding the project removed all references to left (very silly join....) make it an all join if (transformed->hasAttribute(lookupAtom) && !exprReferencesDataset(&args.item(2), newLeft)) args.append(*createAttribute(allAtom)); - DBGLOG("Optimizer: Merge %s and %s", queryNode0Text(transformed), queryNode1Text(child)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Merge %s and %s", queryNode0Text(transformed), queryNode1Text(child)); noteUnused(child); OwnedHqlExpr merged = transformed->clone(args); @@ -4255,7 +4309,8 @@ IHqlExpression * CTreeOptimizer::doCreateTransformed(IHqlExpression * transforme topnArgs.add(*LINK(index), 2); OwnedHqlExpr topn = createDataset(no_topn, topnArgs); incUsage(topn); - DBGLOG("Optimizer: Replace %s with %s", queryNode0Text(child), queryNode1Text(topn)); + if (doTrace(traceOptimizations)) + DBGLOG("Optimizer: Replace %s with %s", queryNode0Text(child), queryNode1Text(topn)); HqlExprArray selectnArgs; selectnArgs.append(*child->cloneAllAnnotations(topn)); unwindChildren(selectnArgs, transformed, 1); diff --git a/ecl/hqlcpp/hqlcpp.cpp b/ecl/hqlcpp/hqlcpp.cpp index fde385d0a18..5009be858f4 100644 --- a/ecl/hqlcpp/hqlcpp.cpp +++ b/ecl/hqlcpp/hqlcpp.cpp @@ -1946,6 +1946,8 @@ void HqlCppTranslator::cacheOptions() } } + updateTraceFlags(loadTraceFlags(wu(), eclccTraceOptions, queryTraceFlags())); + //Configure the divide by zero action options.divideByZeroAction = DBZzero; const char * dbz = wu()->getDebugValue("divideByZero",val).str(); diff --git a/ecl/hqlcpp/hqlhtcpp.cpp b/ecl/hqlcpp/hqlhtcpp.cpp index ea303805449..b31e9667a7e 100644 --- a/ecl/hqlcpp/hqlhtcpp.cpp +++ b/ecl/hqlcpp/hqlhtcpp.cpp @@ -8740,6 +8740,8 @@ ABoundActivity * HqlCppTranslator::doBuildActivitySplit(BuildCtx & ctx, IHqlExpr instance->addConstructorParameter(numWays); instance->addConstructorParameter(queryBoolExpr(balanced)); } + if (balanced) + instance->addAttributeBool(WaIsBalanced, true); buildInstanceSuffix(instance); buildConnectInputOutput(ctx, instance, boundDataset, 0, 0); diff --git a/ecl/hqlcpp/hqliproj.cpp b/ecl/hqlcpp/hqliproj.cpp index 22c8e6b8561..77b26c9a8bd 100644 --- a/ecl/hqlcpp/hqliproj.cpp +++ b/ecl/hqlcpp/hqliproj.cpp @@ -2729,7 +2729,8 @@ void ImplicitProjectTransformer::logChange(const char * message, IHqlExpression const char * const format = "ImplicitProject: %s %s now %s"; - DBGLOG(format, message, name.str(), fieldText.str()); + if (doTrace(traceOptimizations)) + DBGLOG(format, message, name.str(), fieldText.str()); if (options.notifyOptimizedProjects) { if (options.notifyOptimizedProjects >= 2 || exprName) @@ -3101,10 +3102,13 @@ void ImplicitProjectTransformer::finalizeFields(IHqlExpression * expr) extra->fieldsToBlank.optimizeFieldsToBlank(extra->outputFields, queryNewColumnProvider(expr)); if (!extra->fieldsToBlank.isEmpty()) { - const char * opString = getOpString(expr->getOperator()); - StringBuffer fieldText; - extra->fieldsToBlank.getText(fieldText); - DBGLOG("ImplicitProject: Fields %s for %s not required by outputs - so blank in transform", fieldText.str(), opString); + if (doTrace(traceOptimizations)) + { + const char * opString = getOpString(expr->getOperator()); + StringBuffer fieldText; + extra->fieldsToBlank.getText(fieldText); + DBGLOG("ImplicitProject: Fields %s for %s not required by outputs - so blank in transform", fieldText.str(), opString); + } } extra->finalizeOutputRecord(false); break; diff --git a/ecl/hqlcpp/hqlttcpp.cpp b/ecl/hqlcpp/hqlttcpp.cpp index 49d13191769..87db9e249cc 100644 --- a/ecl/hqlcpp/hqlttcpp.cpp +++ b/ecl/hqlcpp/hqlttcpp.cpp @@ -6493,7 +6493,9 @@ IHqlExpression * WorkflowTransformer::extractCommonWorkflow(IHqlExpression * exp if (expr->queryName()) s.append("[").append(expr->queryName()).append("] "); s.append(" to common up code between workflow items"); - DBGLOG("%s", s.str()); + if (doTrace(traceOptimizations)) + DBGLOG("%s", s.str()); + translator.addWorkunitException(SeverityInformation, HQLWRN_TryAddingIndependent, s.str(), location); if (!translator.queryOptions().performWorkflowCse) return LINK(transformed); @@ -6503,11 +6505,12 @@ IHqlExpression * WorkflowTransformer::extractCommonWorkflow(IHqlExpression * exp // e.g., ensure it really is worth commoning up, the expressions aren't to be evaluated on different clusters etc. etc. unsigned wfid = ++wfidCount; - s.appendf("AutoWorkflow: Spotted %s ", getOpString(expr->getOperator())); + s.clear().appendf("AutoWorkflow: Spotted %s ", getOpString(expr->getOperator())); if (expr->queryId()) s.append("[").append(expr->queryId()->queryStr()).append("] "); s.append(" to common up between workflow items [").append(wfid).append("]"); - DBGLOG("%s", s.str()); + if (doTrace(traceOptimizations)) + DBGLOG("%s", s.str()); translator.addWorkunitException(SeverityInformation, 0, s.str(), location); GlobalAttributeInfo info("jobtemp::wfa", "wfa", transformed); @@ -8832,12 +8835,15 @@ IHqlExpression * AutoScopeMigrateTransformer::createTransformed(IHqlExpression * AutoScopeMigrateInfo * extra = queryBodyExtra(expr); if (extra->doAutoHoist(transformed, translator.queryOptions().minimizeWorkunitTemporaries)) { - StringBuffer s; - s.appendf("AutoGlobal: Spotted %s ", getOpString(expr->getOperator())); - if (expr->queryName()) - s.append("[").append(expr->queryName()).append("] "); - s.append("as an item to hoist"); - DBGLOG("%s", s.str()); + if (doTrace(traceOptimizations)) + { + StringBuffer s; + s.appendf("AutoGlobal: Spotted %s ", getOpString(expr->getOperator())); + if (expr->queryName()) + s.append("[").append(expr->queryName()).append("] "); + s.append("as an item to hoist"); + DBGLOG("%s", s.str()); + } if (extra->globalInsideChild) { StringBuffer nameText; @@ -9394,7 +9400,8 @@ IHqlExpression * KeyedProjectTransformer::createTransformed(IHqlExpression * exp expandedTransform.setown(mapper.expandFields(transformed->queryChild(3), oldRight, newRight)); if (translatedFilter && (expandedTransform || op == no_keyeddistribute)) { - DBGLOG("KeyedProjectTransformer: Merge KEYED PROJECT into JOIN"); + if (doTrace(traceOptimizations)) + DBGLOG("KeyedProjectTransformer: Merge KEYED PROJECT into JOIN"); HqlExprArray args; args.append(*LINK(transformed->queryChild(0))); args.append(*LINK(rhs->queryChild(0))); diff --git a/ecl/regress/regress.sh b/ecl/regress/regress.sh index 8a6ab7a45c5..95b7b9f525c 100755 --- a/ecl/regress/regress.sh +++ b/ecl/regress/regress.sh @@ -122,7 +122,7 @@ fi if [[ $eclcc != '' ]]; then ## Set flags default_flags="-P$target_dir -legacy -platform=thorlcr -fforceGenerate -fregressionTest -b -S -shared -meta+" - flags="$default_flags $include_dir -fshowMetaInGraph -fspanMultipleCpp- $userflags" + flags="$default_flags $include_dir -fshowMetaInGraph -fspanMultipleCpp- -ftraceOptimizations $userflags" ## Prepare target directory if [[ $query == '' ]]; then diff --git a/esp/src/package-lock.json b/esp/src/package-lock.json index 42a17c1b2cc..bb858aa4279 100644 --- a/esp/src/package-lock.json +++ b/esp/src/package-lock.json @@ -9,16 +9,16 @@ "version": "1.0.0", "license": "Apache-2.0", "dependencies": { - "@fluentui/react": "8.117.0", - "@fluentui/react-components": "9.47.2", - "@fluentui/react-experiments": "8.14.137", - "@fluentui/react-hooks": "8.7.0", - "@fluentui/react-icons-mdl2": "1.3.59", - "@fluentui/react-migration-v8-v9": "9.6.3", + "@fluentui/react": "8.119.3", + "@fluentui/react-components": "9.54.6", + "@fluentui/react-experiments": "8.14.160", + "@fluentui/react-hooks": "8.8.10", + "@fluentui/react-icons-mdl2": "1.3.72", + "@fluentui/react-migration-v8-v9": "9.6.22", "@hpcc-js/chart": "2.83.4", "@hpcc-js/codemirror": "2.62.1", "@hpcc-js/common": "2.71.18", - "@hpcc-js/comms": "2.93.0", + "@hpcc-js/comms": "2.94.0", "@hpcc-js/dataflow": "8.1.7", "@hpcc-js/eclwatch": "2.74.8", "@hpcc-js/graph": "2.85.16", @@ -138,9 +138,10 @@ } }, "node_modules/@emotion/hash": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz", - "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==" + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", @@ -209,11 +210,12 @@ } }, "node_modules/@floating-ui/core": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz", - "integrity": "sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==", + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.5.tgz", + "integrity": "sha512-8GrTWmoFhm5BsMZOTHeGD2/0FLKLQQHvO/ZmQga4tKempYRLz8aqJGqXVuQgisnMObq2YZ2SgkwctN1LOOxcqA==", + "license": "MIT", "dependencies": { - "@floating-ui/utils": "^0.2.1" + "@floating-ui/utils": "^0.2.5" } }, "node_modules/@floating-ui/devtools": { @@ -225,75 +227,83 @@ } }, "node_modules/@floating-ui/dom": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.3.tgz", - "integrity": "sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==", + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.8.tgz", + "integrity": "sha512-kx62rP19VZ767Q653wsP1XZCGIirkE09E0QUGNYTM/ttbbQHqcGPdSfWFxUyyNLc/W6aoJRBajOSXhP6GXjC0Q==", + "license": "MIT", "dependencies": { - "@floating-ui/core": "^1.0.0", - "@floating-ui/utils": "^0.2.0" + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.5" } }, "node_modules/@floating-ui/utils": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", - "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==" + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.5.tgz", + "integrity": "sha512-sTcG+QZ6fdEUObICavU+aB3Mp8HY4n14wYHdxK4fXjPmv3PXZZeY5RaguJmGyeH/CJQhX3fqKUtS4qc1LoHwhQ==", + "license": "MIT" }, "node_modules/@fluentui/date-time-utilities": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/@fluentui/date-time-utilities/-/date-time-utilities-8.6.0.tgz", - "integrity": "sha512-fpdR2pesIewgfgErX0eS+YBPengNH4Qf21Q6bNFTfQsBYXaJZMUqD4V/57JRfIFz4fYXZ6lWKFTTQe50wXKlWA==", + "version": "8.6.9", + "resolved": "https://registry.npmjs.org/@fluentui/date-time-utilities/-/date-time-utilities-8.6.9.tgz", + "integrity": "sha512-dgOlVm4nXBWDLqijmvn4iAtyv1hVpQZjN6p0So74BW+7ASUTkQGe3lf8PHV/OjBiXfZa4qwONvmTQBGCheNU0w==", + "license": "MIT", "dependencies": { - "@fluentui/set-version": "^8.2.14", + "@fluentui/set-version": "^8.2.23", "tslib": "^2.1.0" } }, "node_modules/@fluentui/dom-utilities": { - "version": "2.2.14", - "resolved": "https://registry.npmjs.org/@fluentui/dom-utilities/-/dom-utilities-2.2.14.tgz", - "integrity": "sha512-+4DVm5sNfJh+l8fM+7ylpOkGNZkNr4X1z1uKQPzRJ1PRhlnvc6vLpWNNicGwpjTbgufSrVtGKXwP5sf++r81lg==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/@fluentui/dom-utilities/-/dom-utilities-2.3.7.tgz", + "integrity": "sha512-AaTR9BhJEF0i042NS1Ju8l95f24p2tBMq6jVVbUEDtYnKaxWnpv8R9eYjOwy8SDniQc1ino+BkolIgCVXXvDmw==", + "license": "MIT", "dependencies": { - "@fluentui/set-version": "^8.2.14", + "@fluentui/set-version": "^8.2.23", "tslib": "^2.1.0" } }, "node_modules/@fluentui/example-data": { - "version": "8.4.15", - "resolved": "https://registry.npmjs.org/@fluentui/example-data/-/example-data-8.4.15.tgz", - "integrity": "sha512-NasmufMLRzJm8ACxunAuTKrgyv0aFhBu/hT1XTXZSVKrgyU+hPUTUF4v8r1L1ekUtrV185y6iZTA00eY2MqboA==", + "version": "8.4.24", + "resolved": "https://registry.npmjs.org/@fluentui/example-data/-/example-data-8.4.24.tgz", + "integrity": "sha512-eoEC6a8yzUdbaLflPiL8LDPLHo/U32EACHncUaEzoHrI/FilzlsztIQ9qY7AB5k8MIhbfEBm3QkQnn6/iVyt3w==", + "license": "MIT", "dependencies": { "tslib": "^2.1.0" } }, "node_modules/@fluentui/fluent2-theme": { - "version": "8.107.68", - "resolved": "https://registry.npmjs.org/@fluentui/fluent2-theme/-/fluent2-theme-8.107.68.tgz", - "integrity": "sha512-AwE1vDOm3IzVvG0pnL2DyOS1mLLGzktOlZIC2Q5BoNkYDbn91eFDN6jE11PuARvCPSRjEqumjkQU0egvF48Dsw==", + "version": "8.107.91", + "resolved": "https://registry.npmjs.org/@fluentui/fluent2-theme/-/fluent2-theme-8.107.91.tgz", + "integrity": "sha512-La9on84Q+A9kTH/qLgyCnwjMAC/iZo/rbkO77R5uhAaZRs6jtuusDf+JhbWI0hHBUHDdFKg8k403scDcnbg8Jg==", + "license": "MIT", "dependencies": { - "@fluentui/react": "^8.117.0", - "@fluentui/set-version": "^8.2.14", + "@fluentui/react": "^8.119.3", + "@fluentui/set-version": "^8.2.23", "tslib": "^2.1.0" } }, "node_modules/@fluentui/font-icons-mdl2": { - "version": "8.5.34", - "resolved": "https://registry.npmjs.org/@fluentui/font-icons-mdl2/-/font-icons-mdl2-8.5.34.tgz", - "integrity": "sha512-FRTtryqrU0ilzSATvfU3zG7x8+mnEIrio3Qd6LDsQ7cLm62BX2z25rBqTvmY2YmY/fHFx2tkVsTZcCKRT08Vww==", + "version": "8.5.47", + "resolved": "https://registry.npmjs.org/@fluentui/font-icons-mdl2/-/font-icons-mdl2-8.5.47.tgz", + "integrity": "sha512-99d/cjEMz0ik9LnVrEDhZB4CnQavwgBvZuNa/EAaeHZMlQ7eheCzU3PNG4goPC7o4yg7XCNyngA7hEx3RUPUDA==", + "license": "MIT", "dependencies": { - "@fluentui/set-version": "^8.2.14", - "@fluentui/style-utilities": "^8.10.5", - "@fluentui/utilities": "^8.15.0", + "@fluentui/set-version": "^8.2.23", + "@fluentui/style-utilities": "^8.10.18", + "@fluentui/utilities": "^8.15.13", "tslib": "^2.1.0" } }, "node_modules/@fluentui/foundation-legacy": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@fluentui/foundation-legacy/-/foundation-legacy-8.4.0.tgz", - "integrity": "sha512-RSOfVPLgCCru6RBYxqp754aeWZ+lYVI+CMffMrhg2/LGl0CcNGXZjK0LYH/anbVq1sEZ/D9e56FekzMOR3CScw==", + "version": "8.4.13", + "resolved": "https://registry.npmjs.org/@fluentui/foundation-legacy/-/foundation-legacy-8.4.13.tgz", + "integrity": "sha512-LIrqiDM0Fe45XLIx/XISwRfcaB5TfoMlkjic7K6goZtssi6VSNEAWjj+V2DOZNUaaFE3J3j61EspoZEKbqGazg==", + "license": "MIT", "dependencies": { - "@fluentui/merge-styles": "^8.6.0", - "@fluentui/set-version": "^8.2.14", - "@fluentui/style-utilities": "^8.10.5", - "@fluentui/utilities": "^8.15.0", + "@fluentui/merge-styles": "^8.6.12", + "@fluentui/set-version": "^8.2.23", + "@fluentui/style-utilities": "^8.10.18", + "@fluentui/utilities": "^8.15.13", "tslib": "^2.1.0" }, "peerDependencies": { @@ -302,9 +312,10 @@ } }, "node_modules/@fluentui/keyboard-key": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/@fluentui/keyboard-key/-/keyboard-key-0.4.14.tgz", - "integrity": "sha512-XzZHcyFEM20H23h3i15UpkHi2AhRBriXPGAHq0Jm98TKFppXehedjjEFuUsh+CyU5JKBhDalWp8TAQ1ArpNzow==", + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/@fluentui/keyboard-key/-/keyboard-key-0.4.23.tgz", + "integrity": "sha512-9GXeyUqNJUdg5JiQUZeGPiKnRzMRi9YEUn1l9zq6X/imYdMhxHrxpVZS12129cBfgvPyxt9ceJpywSfmLWqlKA==", + "license": "MIT", "dependencies": { "tslib": "^2.1.0" } @@ -313,44 +324,48 @@ "version": "9.0.7", "resolved": "https://registry.npmjs.org/@fluentui/keyboard-keys/-/keyboard-keys-9.0.7.tgz", "integrity": "sha512-vaQ+lOveQTdoXJYqDQXWb30udSfTVcIuKk1rV0X0eGAgcHeSDeP1HxMy+OgHOQZH3OiBH4ZYeWxb+tmfiDiygQ==", + "license": "MIT", "dependencies": { "@swc/helpers": "^0.5.1" } }, "node_modules/@fluentui/merge-styles": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/@fluentui/merge-styles/-/merge-styles-8.6.0.tgz", - "integrity": "sha512-Si54VVK/XZQMTPT6aKE/RmqsY7uy9hERreU143Fbqtg9cf+Hr4iJ7FOGC4dXCfrFIXs0KvIHXCh5mtfrEW2aRQ==", + "version": "8.6.12", + "resolved": "https://registry.npmjs.org/@fluentui/merge-styles/-/merge-styles-8.6.12.tgz", + "integrity": "sha512-v8njux9frUkoGGlBnQXKHaKCX2nLZVHPFMDMzibtAIt4vIkkv+oY2lFmJ2h96tSIkg4eVN7h5sSDTFVoAPwpYg==", + "license": "MIT", "dependencies": { - "@fluentui/set-version": "^8.2.14", + "@fluentui/set-version": "^8.2.23", "tslib": "^2.1.0" } }, "node_modules/@fluentui/priority-overflow": { - "version": "9.1.11", - "resolved": "https://registry.npmjs.org/@fluentui/priority-overflow/-/priority-overflow-9.1.11.tgz", - "integrity": "sha512-sdrpavvKX2kepQ1d6IaI3ObLq5SAQBPRHPGx2+wiMWL7cEx9vGGM0fmeicl3soqqmM5uwCmWnZk9QZv9XOY98w==", + "version": "9.1.13", + "resolved": "https://registry.npmjs.org/@fluentui/priority-overflow/-/priority-overflow-9.1.13.tgz", + "integrity": "sha512-yDojVpkhBZTXOYExrCgW1GXbw3x9pYIS617xlNJIc2t06Cd3H32y2p51QXFt94sBmlVyAvPu7UKBHaq1Yw7u+w==", + "license": "MIT", "dependencies": { "@swc/helpers": "^0.5.1" } }, "node_modules/@fluentui/react": { - "version": "8.117.0", - "resolved": "https://registry.npmjs.org/@fluentui/react/-/react-8.117.0.tgz", - "integrity": "sha512-7zPFyyhm6zZGUF5C7/L5XNIBNBYsGc2ZIVg+nuCd56hBgMh0smWDnbciFKpQ9jvAh6gvfKbsgUuVWrD/FwEkAg==", - "dependencies": { - "@fluentui/date-time-utilities": "^8.6.0", - "@fluentui/font-icons-mdl2": "^8.5.34", - "@fluentui/foundation-legacy": "^8.4.0", - "@fluentui/merge-styles": "^8.6.0", - "@fluentui/react-focus": "^8.8.42", - "@fluentui/react-hooks": "^8.7.0", - "@fluentui/react-portal-compat-context": "^9.0.11", - "@fluentui/react-window-provider": "^2.2.18", - "@fluentui/set-version": "^8.2.14", - "@fluentui/style-utilities": "^8.10.5", - "@fluentui/theme": "^2.6.43", - "@fluentui/utilities": "^8.15.0", + "version": "8.119.3", + "resolved": "https://registry.npmjs.org/@fluentui/react/-/react-8.119.3.tgz", + "integrity": "sha512-sJaFQU7sy8/kZR7ma9Ejqi6GnYFVzYcgSA2DVbvxGoH+edoztXQjro9qaIaUILhtWBUMMcG47DtykgubelhQFw==", + "license": "MIT", + "dependencies": { + "@fluentui/date-time-utilities": "^8.6.9", + "@fluentui/font-icons-mdl2": "^8.5.47", + "@fluentui/foundation-legacy": "^8.4.13", + "@fluentui/merge-styles": "^8.6.12", + "@fluentui/react-focus": "^8.9.10", + "@fluentui/react-hooks": "^8.8.10", + "@fluentui/react-portal-compat-context": "^9.0.12", + "@fluentui/react-window-provider": "^2.2.27", + "@fluentui/set-version": "^8.2.23", + "@fluentui/style-utilities": "^8.10.18", + "@fluentui/theme": "^2.6.56", + "@fluentui/utilities": "^8.15.13", "@microsoft/load-themed-styles": "^1.10.26", "tslib": "^2.1.0" }, @@ -362,19 +377,20 @@ } }, "node_modules/@fluentui/react-accordion": { - "version": "9.3.46", - "resolved": "https://registry.npmjs.org/@fluentui/react-accordion/-/react-accordion-9.3.46.tgz", - "integrity": "sha512-bFOF/uoPYL4AUQEIKFTgx8WZgeC39Vw2FiL6A2A0km0Z9yBgWg7LLsF73/MbgoO0GjH8BvO/2ddpgdd433jIRw==", - "dependencies": { - "@fluentui/react-aria": "^9.10.2", - "@fluentui/react-context-selector": "^9.1.56", - "@fluentui/react-icons": "^2.0.224", - "@fluentui/react-jsx-runtime": "^9.0.34", - "@fluentui/react-shared-contexts": "^9.15.2", - "@fluentui/react-tabster": "^9.19.5", + "version": "9.4.4", + "resolved": "https://registry.npmjs.org/@fluentui/react-accordion/-/react-accordion-9.4.4.tgz", + "integrity": "sha512-nDkzWhz9PID94rzg06CiuzSkFuAemT2mIOs5G9/IQqIaQYr2do/Ff7WH5gbB6vK901C1b4ZqxeNnPYNf/wLhpA==", + "license": "MIT", + "dependencies": { + "@fluentui/react-aria": "^9.13.2", + "@fluentui/react-context-selector": "^9.1.65", + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-shared-contexts": "^9.20.0", + "@fluentui/react-tabster": "^9.22.3", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -385,18 +401,19 @@ } }, "node_modules/@fluentui/react-alert": { - "version": "9.0.0-beta.114", - "resolved": "https://registry.npmjs.org/@fluentui/react-alert/-/react-alert-9.0.0-beta.114.tgz", - "integrity": "sha512-ZA55Wf9ZNE2KfKnT9fDqvWqnAKgcrYwYIJoliG+pCLztCitwlv/XUKAWR/DkP02NpA2qEeaiY1D9k/Mwd1haIQ==", - "dependencies": { - "@fluentui/react-avatar": "^9.6.19", - "@fluentui/react-button": "^9.3.73", - "@fluentui/react-icons": "^2.0.224", - "@fluentui/react-jsx-runtime": "^9.0.34", - "@fluentui/react-tabster": "^9.19.5", + "version": "9.0.0-beta.124", + "resolved": "https://registry.npmjs.org/@fluentui/react-alert/-/react-alert-9.0.0-beta.124.tgz", + "integrity": "sha512-yFBo3B5H9hnoaXxlkuz8wRz04DEyQ+ElYA/p5p+Vojf19Zuta8DmFZZ6JtWdtxcdnnQ4LvAfC5OYYlzdReozPA==", + "license": "MIT", + "dependencies": { + "@fluentui/react-avatar": "^9.6.29", + "@fluentui/react-button": "^9.3.83", + "@fluentui/react-icons": "^2.0.239", + "@fluentui/react-jsx-runtime": "^9.0.39", + "@fluentui/react-tabster": "^9.21.5", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", + "@fluentui/react-utilities": "^9.18.10", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -407,15 +424,16 @@ } }, "node_modules/@fluentui/react-aria": { - "version": "9.10.2", - "resolved": "https://registry.npmjs.org/@fluentui/react-aria/-/react-aria-9.10.2.tgz", - "integrity": "sha512-M8wzxPZlMOLr7SlZXlSi/zCbLSsXrJzpMjLkTOPPlMrMu8He38oM6Djc4dCac/cZn8ERpKUDaoAK5JF/kbtLzQ==", + "version": "9.13.2", + "resolved": "https://registry.npmjs.org/@fluentui/react-aria/-/react-aria-9.13.2.tgz", + "integrity": "sha512-lb93r/FiE3bj1/lfixy/Hb+LW8MQ9HCqdDWLRo1gmP4f3QgIj/Gz7oTB+NilwzytiH4OBDXq0apdUHGLwGkotA==", + "license": "MIT", "dependencies": { "@fluentui/keyboard-keys": "^9.0.7", - "@fluentui/react-jsx-runtime": "^9.0.34", - "@fluentui/react-shared-contexts": "^9.15.2", - "@fluentui/react-tabster": "^9.19.5", - "@fluentui/react-utilities": "^9.18.5", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-shared-contexts": "^9.20.0", + "@fluentui/react-tabster": "^9.22.3", + "@fluentui/react-utilities": "^9.18.13", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -426,21 +444,22 @@ } }, "node_modules/@fluentui/react-avatar": { - "version": "9.6.19", - "resolved": "https://registry.npmjs.org/@fluentui/react-avatar/-/react-avatar-9.6.19.tgz", - "integrity": "sha512-3/8BBoPXNGfcuNVN4+bpwpd124CEdFEm9VKD6hQ6VmIHM6phBWnQc6J7djuKlZTw7B5UEeqEOEZgMJeGUx27SA==", - "dependencies": { - "@fluentui/react-badge": "^9.2.29", - "@fluentui/react-context-selector": "^9.1.56", - "@fluentui/react-icons": "^2.0.224", - "@fluentui/react-jsx-runtime": "^9.0.34", - "@fluentui/react-popover": "^9.9.2", - "@fluentui/react-shared-contexts": "^9.15.2", - "@fluentui/react-tabster": "^9.19.5", + "version": "9.6.33", + "resolved": "https://registry.npmjs.org/@fluentui/react-avatar/-/react-avatar-9.6.33.tgz", + "integrity": "sha512-xikMYnjtBQRv1rHOhDEc/5GvG5F46MFhgu3jcBbxyVt512AfwVgDMPj18tg4y2RaZ587FLPFifK7VlNBDAaT4g==", + "license": "MIT", + "dependencies": { + "@fluentui/react-badge": "^9.2.41", + "@fluentui/react-context-selector": "^9.1.65", + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-popover": "^9.9.15", + "@fluentui/react-shared-contexts": "^9.20.0", + "@fluentui/react-tabster": "^9.22.3", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-tooltip": "^9.4.21", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", + "@fluentui/react-tooltip": "^9.4.34", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -451,16 +470,17 @@ } }, "node_modules/@fluentui/react-badge": { - "version": "9.2.29", - "resolved": "https://registry.npmjs.org/@fluentui/react-badge/-/react-badge-9.2.29.tgz", - "integrity": "sha512-k2CMMzBLPCNq5WAUfkCvWqCPeh8/NsfLxQBre8klxFZS5TT872ViLwmYHXpHWTfFymFrChaedOd7C8ZYqeT4tA==", + "version": "9.2.41", + "resolved": "https://registry.npmjs.org/@fluentui/react-badge/-/react-badge-9.2.41.tgz", + "integrity": "sha512-/GBKotH68XrBix1mCJybYHw+5QKYoAbINqYPj2mEfcDiC2VfS4w4Drjokcp2O1KD2cX/YZ9PThptKmWkjT3UOw==", + "license": "MIT", "dependencies": { - "@fluentui/react-icons": "^2.0.224", - "@fluentui/react-jsx-runtime": "^9.0.34", - "@fluentui/react-shared-contexts": "^9.15.2", + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-shared-contexts": "^9.20.0", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -471,20 +491,21 @@ } }, "node_modules/@fluentui/react-breadcrumb": { - "version": "9.0.19", - "resolved": "https://registry.npmjs.org/@fluentui/react-breadcrumb/-/react-breadcrumb-9.0.19.tgz", - "integrity": "sha512-12pqa0CAVDaBu16/E/aU9R+wJoBga/4Dm9UoyX4FG5TkoM28kiOGXZ8xhEdOrF4w1J9hFfEx2cyNGSdGFyJfcQ==", - "dependencies": { - "@fluentui/react-aria": "^9.10.2", - "@fluentui/react-button": "^9.3.73", - "@fluentui/react-icons": "^2.0.224", - "@fluentui/react-jsx-runtime": "^9.0.34", - "@fluentui/react-link": "^9.2.15", - "@fluentui/react-shared-contexts": "^9.15.2", - "@fluentui/react-tabster": "^9.19.5", + "version": "9.0.33", + "resolved": "https://registry.npmjs.org/@fluentui/react-breadcrumb/-/react-breadcrumb-9.0.33.tgz", + "integrity": "sha512-VDnTsUPqmWRAuOwXwScItlaLzuMFlOXCRgrZuU3py8QTTjUU4jIBi2X7wI7DREpD0FRM7wbujCkN0tb4lRO4FQ==", + "license": "MIT", + "dependencies": { + "@fluentui/react-aria": "^9.13.2", + "@fluentui/react-button": "^9.3.87", + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-link": "^9.2.28", + "@fluentui/react-shared-contexts": "^9.20.0", + "@fluentui/react-tabster": "^9.22.3", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -495,19 +516,20 @@ } }, "node_modules/@fluentui/react-button": { - "version": "9.3.73", - "resolved": "https://registry.npmjs.org/@fluentui/react-button/-/react-button-9.3.73.tgz", - "integrity": "sha512-VsCxj4pKWL1SVj0XlYBRs4kaFUfRVK3JqCWx9mlDuHYzeRzk4aBCBT5vBIzrrPTj3bR2yl/zOf6m5T43kyWZxw==", + "version": "9.3.87", + "resolved": "https://registry.npmjs.org/@fluentui/react-button/-/react-button-9.3.87.tgz", + "integrity": "sha512-Di8RWjIswa1jriYfed6FH90fqmTwBkaILWxzJzChaBbUAOtxEYn3K57F+9PS9s05z7PhlDuVnfd2RV0dIrYHtg==", + "license": "MIT", "dependencies": { "@fluentui/keyboard-keys": "^9.0.7", - "@fluentui/react-aria": "^9.10.2", - "@fluentui/react-icons": "^2.0.224", - "@fluentui/react-jsx-runtime": "^9.0.34", - "@fluentui/react-shared-contexts": "^9.15.2", - "@fluentui/react-tabster": "^9.19.5", + "@fluentui/react-aria": "^9.13.2", + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-shared-contexts": "^9.20.0", + "@fluentui/react-tabster": "^9.22.3", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -518,16 +540,17 @@ } }, "node_modules/@fluentui/react-card": { - "version": "9.0.72", - "resolved": "https://registry.npmjs.org/@fluentui/react-card/-/react-card-9.0.72.tgz", - "integrity": "sha512-sJQ0T0SOBZ8tTGMxmJhVYDaHsQe/+ECQwhPIb0irDnD3ojTbL/IjxONeBnxVJ5/xG6cA3rV6tfD8WrockIDXOg==", + "version": "9.0.87", + "resolved": "https://registry.npmjs.org/@fluentui/react-card/-/react-card-9.0.87.tgz", + "integrity": "sha512-aH7tvf1XTtW5kVM2YzbM1OEVQ0dn9POBHEutmpSkHpxb/Wa4bAPm4Yrimt9PZqcFws1WdFapbZD0xaYGhf+9Ew==", + "license": "MIT", "dependencies": { "@fluentui/keyboard-keys": "^9.0.7", - "@fluentui/react-jsx-runtime": "^9.0.34", - "@fluentui/react-tabster": "^9.19.5", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-tabster": "^9.22.3", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -538,19 +561,20 @@ } }, "node_modules/@fluentui/react-checkbox": { - "version": "9.2.18", - "resolved": "https://registry.npmjs.org/@fluentui/react-checkbox/-/react-checkbox-9.2.18.tgz", - "integrity": "sha512-m4UjLx5jMYj0WtPzwNRR0hbGX9NHZvvxf52Xka39DphAorB5ohuTfJe12cMKSwStaOQBa4gtGXntl7tqP02PHQ==", - "dependencies": { - "@fluentui/react-field": "^9.1.59", - "@fluentui/react-icons": "^2.0.224", - "@fluentui/react-jsx-runtime": "^9.0.34", - "@fluentui/react-label": "^9.1.66", - "@fluentui/react-shared-contexts": "^9.15.2", - "@fluentui/react-tabster": "^9.19.5", + "version": "9.2.32", + "resolved": "https://registry.npmjs.org/@fluentui/react-checkbox/-/react-checkbox-9.2.32.tgz", + "integrity": "sha512-q75W+SaGxKHFNjInGq/TYARQHeP7x1H2N0681JFhvo6Ji0BbCTKpxMTsssHvR6OlqBPjcXfkxpXiVD/V8vVgNA==", + "license": "MIT", + "dependencies": { + "@fluentui/react-field": "^9.1.71", + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-label": "^9.1.74", + "@fluentui/react-shared-contexts": "^9.20.0", + "@fluentui/react-tabster": "^9.22.3", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -561,23 +585,24 @@ } }, "node_modules/@fluentui/react-combobox": { - "version": "9.9.4", - "resolved": "https://registry.npmjs.org/@fluentui/react-combobox/-/react-combobox-9.9.4.tgz", - "integrity": "sha512-3SmoCLyBa7+iuwrbVkjIVrOuTAeCYK7nvwWZfTf5eqeEsYmk+6orXy0kz1lR23M5Mtfa8l4FqvopUiuyAmXuCw==", + "version": "9.13.2", + "resolved": "https://registry.npmjs.org/@fluentui/react-combobox/-/react-combobox-9.13.2.tgz", + "integrity": "sha512-unLJyLs0rq06cl81ka89JkvKo3iXXGHC5wbZ4KTAEF3ZoXjuw7EI19tLiR+FIEV60qhSo2jOXKCF8xpHOKWIXQ==", + "license": "MIT", "dependencies": { "@fluentui/keyboard-keys": "^9.0.7", - "@fluentui/react-aria": "^9.10.2", - "@fluentui/react-context-selector": "^9.1.56", - "@fluentui/react-field": "^9.1.59", - "@fluentui/react-icons": "^2.0.224", - "@fluentui/react-jsx-runtime": "^9.0.34", - "@fluentui/react-portal": "^9.4.18", - "@fluentui/react-positioning": "^9.14.2", - "@fluentui/react-shared-contexts": "^9.15.2", - "@fluentui/react-tabster": "^9.19.5", + "@fluentui/react-aria": "^9.13.2", + "@fluentui/react-context-selector": "^9.1.65", + "@fluentui/react-field": "^9.1.71", + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-portal": "^9.4.31", + "@fluentui/react-positioning": "^9.15.6", + "@fluentui/react-shared-contexts": "^9.20.0", + "@fluentui/react-tabster": "^9.22.3", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -588,62 +613,68 @@ } }, "node_modules/@fluentui/react-components": { - "version": "9.47.2", - "resolved": "https://registry.npmjs.org/@fluentui/react-components/-/react-components-9.47.2.tgz", - "integrity": "sha512-2DHL03T6flpIqTWghQQ5ADmb2VXnfNAxz2mC9iL+RRHeTYeDf9PweOIiCowU2gioyGvV9VfKQbXP5WG5A7ijoQ==", - "dependencies": { - "@fluentui/react-accordion": "^9.3.46", - "@fluentui/react-alert": "9.0.0-beta.114", - "@fluentui/react-aria": "^9.10.2", - "@fluentui/react-avatar": "^9.6.19", - "@fluentui/react-badge": "^9.2.29", - "@fluentui/react-breadcrumb": "^9.0.19", - "@fluentui/react-button": "^9.3.73", - "@fluentui/react-card": "^9.0.72", - "@fluentui/react-checkbox": "^9.2.18", - "@fluentui/react-combobox": "^9.9.4", - "@fluentui/react-dialog": "^9.9.15", - "@fluentui/react-divider": "^9.2.65", - "@fluentui/react-drawer": "^9.1.9", - "@fluentui/react-field": "^9.1.59", - "@fluentui/react-image": "^9.1.62", - "@fluentui/react-infobutton": "9.0.0-beta.98", - "@fluentui/react-infolabel": "^9.0.26", - "@fluentui/react-input": "^9.4.69", - "@fluentui/react-label": "^9.1.66", - "@fluentui/react-link": "^9.2.15", - "@fluentui/react-menu": "^9.13.5", - "@fluentui/react-message-bar": "^9.0.24", - "@fluentui/react-overflow": "^9.1.15", - "@fluentui/react-persona": "^9.2.78", - "@fluentui/react-popover": "^9.9.2", - "@fluentui/react-portal": "^9.4.18", - "@fluentui/react-positioning": "^9.14.2", - "@fluentui/react-progress": "^9.1.69", - "@fluentui/react-provider": "^9.13.16", - "@fluentui/react-radio": "^9.2.13", - "@fluentui/react-rating": "^9.0.1", - "@fluentui/react-select": "^9.1.69", - "@fluentui/react-shared-contexts": "^9.15.2", - "@fluentui/react-skeleton": "^9.0.57", - "@fluentui/react-slider": "^9.1.75", - "@fluentui/react-spinbutton": "^9.2.69", - "@fluentui/react-spinner": "^9.4.2", - "@fluentui/react-switch": "^9.1.75", - "@fluentui/react-table": "^9.12.0", - "@fluentui/react-tabs": "^9.4.14", - "@fluentui/react-tabster": "^9.19.5", - "@fluentui/react-tags": "^9.2.0", - "@fluentui/react-text": "^9.4.14", - "@fluentui/react-textarea": "^9.3.69", + "version": "9.54.6", + "resolved": "https://registry.npmjs.org/@fluentui/react-components/-/react-components-9.54.6.tgz", + "integrity": "sha512-ocL+VIdTZsyjrA8z7IWifoyH7YFAsLLn2FPFdLkQqPk/+XPcbuduRDBYfeVAqwsQLmNk44dC5RXZyziigLUd/A==", + "license": "MIT", + "dependencies": { + "@fluentui/react-accordion": "^9.4.4", + "@fluentui/react-alert": "9.0.0-beta.124", + "@fluentui/react-aria": "^9.13.2", + "@fluentui/react-avatar": "^9.6.33", + "@fluentui/react-badge": "^9.2.41", + "@fluentui/react-breadcrumb": "^9.0.33", + "@fluentui/react-button": "^9.3.87", + "@fluentui/react-card": "^9.0.87", + "@fluentui/react-checkbox": "^9.2.32", + "@fluentui/react-combobox": "^9.13.2", + "@fluentui/react-dialog": "^9.11.6", + "@fluentui/react-divider": "^9.2.73", + "@fluentui/react-drawer": "^9.5.6", + "@fluentui/react-field": "^9.1.71", + "@fluentui/react-image": "^9.1.71", + "@fluentui/react-infobutton": "9.0.0-beta.102", + "@fluentui/react-infolabel": "^9.0.40", + "@fluentui/react-input": "^9.4.83", + "@fluentui/react-label": "^9.1.74", + "@fluentui/react-link": "^9.2.28", + "@fluentui/react-menu": "^9.14.11", + "@fluentui/react-message-bar": "^9.2.6", + "@fluentui/react-motion": "^9.4.0", + "@fluentui/react-overflow": "^9.1.25", + "@fluentui/react-persona": "^9.2.92", + "@fluentui/react-popover": "^9.9.15", + "@fluentui/react-portal": "^9.4.31", + "@fluentui/react-positioning": "^9.15.6", + "@fluentui/react-progress": "^9.1.82", + "@fluentui/react-provider": "^9.17.0", + "@fluentui/react-radio": "^9.2.27", + "@fluentui/react-rating": "^9.0.15", + "@fluentui/react-search": "^9.0.12", + "@fluentui/react-select": "^9.1.82", + "@fluentui/react-shared-contexts": "^9.20.0", + "@fluentui/react-skeleton": "^9.1.10", + "@fluentui/react-slider": "^9.1.89", + "@fluentui/react-spinbutton": "^9.2.83", + "@fluentui/react-spinner": "^9.4.12", + "@fluentui/react-swatch-picker": "^9.1.6", + "@fluentui/react-switch": "^9.1.89", + "@fluentui/react-table": "^9.15.11", + "@fluentui/react-tabs": "^9.4.27", + "@fluentui/react-tabster": "^9.22.3", + "@fluentui/react-tag-picker": "^9.2.3", + "@fluentui/react-tags": "^9.3.12", + "@fluentui/react-teaching-popover": "^9.1.11", + "@fluentui/react-text": "^9.4.23", + "@fluentui/react-textarea": "^9.3.83", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-toast": "^9.3.35", - "@fluentui/react-toolbar": "^9.1.76", - "@fluentui/react-tooltip": "^9.4.21", - "@fluentui/react-tree": "^9.4.36", - "@fluentui/react-utilities": "^9.18.5", - "@fluentui/react-virtualizer": "9.0.0-alpha.73", - "@griffel/react": "^1.5.14", + "@fluentui/react-toast": "^9.3.51", + "@fluentui/react-toolbar": "^9.1.90", + "@fluentui/react-tooltip": "^9.4.34", + "@fluentui/react-tree": "^9.7.5", + "@fluentui/react-utilities": "^9.18.13", + "@fluentui/react-virtualizer": "9.0.0-alpha.82", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -654,11 +685,12 @@ } }, "node_modules/@fluentui/react-context-selector": { - "version": "9.1.56", - "resolved": "https://registry.npmjs.org/@fluentui/react-context-selector/-/react-context-selector-9.1.56.tgz", - "integrity": "sha512-TzDYTvHRuOB3qKiIBB0NU4mwX/fuxW41I1O9yK7C5Dt4RsexNInGLf5HMxYHWufevDSFhRLuAN+ikTHUMkcNzw==", + "version": "9.1.65", + "resolved": "https://registry.npmjs.org/@fluentui/react-context-selector/-/react-context-selector-9.1.65.tgz", + "integrity": "sha512-hpluiP2NtK01Kx1RdKnJkQr7snbFuFJUwRho3NsuzuX/ea9OaVNEAxcvLMUcwd5nItf5Y5U8i07ib7YX5qchmQ==", + "license": "MIT", "dependencies": { - "@fluentui/react-utilities": "^9.18.5", + "@fluentui/react-utilities": "^9.18.13", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -670,23 +702,24 @@ } }, "node_modules/@fluentui/react-dialog": { - "version": "9.9.15", - "resolved": "https://registry.npmjs.org/@fluentui/react-dialog/-/react-dialog-9.9.15.tgz", - "integrity": "sha512-UVjU7ZKq9117A80GQ/cv+YH/Pql4bN8FH3/GbJd8qwOxtlzOWpN8DOu1mwrj5ahxt3b+tpYsmp1QrqX9nujhMA==", + "version": "9.11.6", + "resolved": "https://registry.npmjs.org/@fluentui/react-dialog/-/react-dialog-9.11.6.tgz", + "integrity": "sha512-a7KQZeRcaOM8PzEHFONIxjHyiZjLwA57+Bm2XdILJsVrNL9cCSz2ChN3zaIGGZ3gfZ+YkNvfcbXAz5sVIXyKVw==", + "license": "MIT", "dependencies": { "@fluentui/keyboard-keys": "^9.0.7", - "@fluentui/react-aria": "^9.10.2", - "@fluentui/react-context-selector": "^9.1.56", - "@fluentui/react-icons": "^2.0.224", - "@fluentui/react-jsx-runtime": "^9.0.34", - "@fluentui/react-portal": "^9.4.18", - "@fluentui/react-shared-contexts": "^9.15.2", - "@fluentui/react-tabster": "^9.19.5", + "@fluentui/react-aria": "^9.13.2", + "@fluentui/react-context-selector": "^9.1.65", + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-motion": "^9.4.0", + "@fluentui/react-portal": "^9.4.31", + "@fluentui/react-shared-contexts": "^9.20.0", + "@fluentui/react-tabster": "^9.22.3", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", - "@swc/helpers": "^0.5.1", - "react-transition-group": "^4.4.1" + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", + "@swc/helpers": "^0.5.1" }, "peerDependencies": { "@types/react": ">=16.14.0 <19.0.0", @@ -696,15 +729,16 @@ } }, "node_modules/@fluentui/react-divider": { - "version": "9.2.65", - "resolved": "https://registry.npmjs.org/@fluentui/react-divider/-/react-divider-9.2.65.tgz", - "integrity": "sha512-jjyvD+GnLACxHhV+eTdn0+X2Yar6NlzNK8q+xdZjuD+yJ5NcWiiD+Dkh5CJUFegkaBTUb2+Fp1pFEEMaCzrHkw==", + "version": "9.2.73", + "resolved": "https://registry.npmjs.org/@fluentui/react-divider/-/react-divider-9.2.73.tgz", + "integrity": "sha512-AmkJPAFEszLbWh7vrV6vV+omnkQgfw1hhVBcTIlLD2b712Tk7GczZC2PXTq0fNKI3Aw8x4sNBbomozJp2y+X7w==", + "license": "MIT", "dependencies": { - "@fluentui/react-jsx-runtime": "^9.0.34", - "@fluentui/react-shared-contexts": "^9.15.2", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-shared-contexts": "^9.20.0", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -715,18 +749,19 @@ } }, "node_modules/@fluentui/react-drawer": { - "version": "9.1.9", - "resolved": "https://registry.npmjs.org/@fluentui/react-drawer/-/react-drawer-9.1.9.tgz", - "integrity": "sha512-5KzOVxRPFJa0oDnp+kfCYJezA4JxsQzporNSmVw/i3/w/L9hCJyOuzrI+ps36Xb3tYymaKsAemC4+NAvs4HD+w==", - "dependencies": { - "@fluentui/react-dialog": "^9.9.15", - "@fluentui/react-jsx-runtime": "^9.0.34", - "@fluentui/react-motion-preview": "^0.5.17", - "@fluentui/react-shared-contexts": "^9.15.2", - "@fluentui/react-tabster": "^9.19.5", + "version": "9.5.6", + "resolved": "https://registry.npmjs.org/@fluentui/react-drawer/-/react-drawer-9.5.6.tgz", + "integrity": "sha512-vfn4G+gRpNETqBFQ3cnWJajKbCyMNeqInzDaeEh7eU+8/YANGIofMU8bPublVcSD5Ew9Ly++GEte4dIGvQiB8A==", + "license": "MIT", + "dependencies": { + "@fluentui/react-dialog": "^9.11.6", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-motion-preview": "^0.5.25", + "@fluentui/react-shared-contexts": "^9.20.0", + "@fluentui/react-tabster": "^9.22.3", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -737,20 +772,21 @@ } }, "node_modules/@fluentui/react-experiments": { - "version": "8.14.137", - "resolved": "https://registry.npmjs.org/@fluentui/react-experiments/-/react-experiments-8.14.137.tgz", - "integrity": "sha512-2RhKX+J9xo8AtWpFATp2dUctNQtdQpJY8ndmSyvgLEvDOnHdTNpGC5j4PsIbGV5FmMIo28K67Ni9CM2mstuHlw==", - "dependencies": { - "@fluentui/example-data": "^8.4.15", - "@fluentui/font-icons-mdl2": "^8.5.34", - "@fluentui/foundation-legacy": "^8.4.0", - "@fluentui/merge-styles": "^8.6.0", - "@fluentui/react": "^8.117.0", - "@fluentui/react-hooks": "^8.7.0", - "@fluentui/set-version": "^8.2.14", - "@fluentui/style-utilities": "^8.10.5", - "@fluentui/theme": "^2.6.43", - "@fluentui/utilities": "^8.15.0", + "version": "8.14.160", + "resolved": "https://registry.npmjs.org/@fluentui/react-experiments/-/react-experiments-8.14.160.tgz", + "integrity": "sha512-LsthZCNwOtI8Dv+pTMhEaacBv8gm9I7fDq4Grw5MMAY5aQpCljcaBXCkHEq/4sOZtDTY7gnKh57c/g+GptCkUQ==", + "license": "MIT", + "dependencies": { + "@fluentui/example-data": "^8.4.24", + "@fluentui/font-icons-mdl2": "^8.5.47", + "@fluentui/foundation-legacy": "^8.4.13", + "@fluentui/merge-styles": "^8.6.12", + "@fluentui/react": "^8.119.3", + "@fluentui/react-hooks": "^8.8.10", + "@fluentui/set-version": "^8.2.23", + "@fluentui/style-utilities": "^8.10.18", + "@fluentui/theme": "^2.6.56", + "@fluentui/utilities": "^8.15.13", "@microsoft/load-themed-styles": "^1.10.26", "deep-assign": "^2.0.0", "prop-types": "^15.7.2", @@ -764,17 +800,18 @@ } }, "node_modules/@fluentui/react-field": { - "version": "9.1.59", - "resolved": "https://registry.npmjs.org/@fluentui/react-field/-/react-field-9.1.59.tgz", - "integrity": "sha512-GrrWrGZes+qxE9JbAk3Z85wvFhM8CAXTu3amUxOHB+zt0QVcHXJHkg7OvEtXgSnFYJEau0aQgQg6ZjeFQkMpAg==", - "dependencies": { - "@fluentui/react-context-selector": "^9.1.56", - "@fluentui/react-icons": "^2.0.224", - "@fluentui/react-jsx-runtime": "^9.0.34", - "@fluentui/react-label": "^9.1.66", + "version": "9.1.71", + "resolved": "https://registry.npmjs.org/@fluentui/react-field/-/react-field-9.1.71.tgz", + "integrity": "sha512-DqvLa3ZPm+vhIvbQrZqV8d2Nr/+dJv3mOxlootqMVu4v1l8K6fux4qUzwXvSyydIx7U73R99sC/iOCic2SYDFw==", + "license": "MIT", + "dependencies": { + "@fluentui/react-context-selector": "^9.1.65", + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-label": "^9.1.74", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -785,15 +822,16 @@ } }, "node_modules/@fluentui/react-focus": { - "version": "8.8.42", - "resolved": "https://registry.npmjs.org/@fluentui/react-focus/-/react-focus-8.8.42.tgz", - "integrity": "sha512-U9KRiBabHyVUfemArukjTH6TDthj/MnDbgAUa55CMdj061ZV/Ria+Fxb1QG+GW3Az6bSaAulJvj/fGuNrawWhA==", - "dependencies": { - "@fluentui/keyboard-key": "^0.4.14", - "@fluentui/merge-styles": "^8.6.0", - "@fluentui/set-version": "^8.2.14", - "@fluentui/style-utilities": "^8.10.5", - "@fluentui/utilities": "^8.15.0", + "version": "8.9.10", + "resolved": "https://registry.npmjs.org/@fluentui/react-focus/-/react-focus-8.9.10.tgz", + "integrity": "sha512-9kV15td8uuYhQS4bTLImxVo75dmbeOK0rZ4gQgOAY/0nKRYwiCLfH9SwQuEa+eCmjsBTNuDlXgghjQJyKFh5+A==", + "license": "MIT", + "dependencies": { + "@fluentui/keyboard-key": "^0.4.23", + "@fluentui/merge-styles": "^8.6.12", + "@fluentui/set-version": "^8.2.23", + "@fluentui/style-utilities": "^8.10.18", + "@fluentui/utilities": "^8.15.13", "tslib": "^2.1.0" }, "peerDependencies": { @@ -802,13 +840,14 @@ } }, "node_modules/@fluentui/react-hooks": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/@fluentui/react-hooks/-/react-hooks-8.7.0.tgz", - "integrity": "sha512-m1/2q+zF/dNj2dWqIl06G88dTEMmiaP40k16b1juyjHXYsVPooxPlUD1l9FLrB8mC3VkpCy/fgASaPqRH8mUGw==", + "version": "8.8.10", + "resolved": "https://registry.npmjs.org/@fluentui/react-hooks/-/react-hooks-8.8.10.tgz", + "integrity": "sha512-Xvnn6uKMsinMg/zo79KBNCDABnl0gpmArQYNQya9FCNRzvmHUCDvuQCqv4IKslvPvuC0Ya8mR2NORm2w0JoZiw==", + "license": "MIT", "dependencies": { - "@fluentui/react-window-provider": "^2.2.18", - "@fluentui/set-version": "^8.2.14", - "@fluentui/utilities": "^8.15.0", + "@fluentui/react-window-provider": "^2.2.27", + "@fluentui/set-version": "^8.2.23", + "@fluentui/utilities": "^8.15.13", "tslib": "^2.1.0" }, "peerDependencies": { @@ -817,9 +856,10 @@ } }, "node_modules/@fluentui/react-icons": { - "version": "2.0.225", - "resolved": "https://registry.npmjs.org/@fluentui/react-icons/-/react-icons-2.0.225.tgz", - "integrity": "sha512-L9phN3bAMlZCa5+/ObGjIO+5GI8M50ym766sraSq92jaJwgAXrCJDLWuDGWZRGrC63DcagtR2culptj3q7gMMg==", + "version": "2.0.249", + "resolved": "https://registry.npmjs.org/@fluentui/react-icons/-/react-icons-2.0.249.tgz", + "integrity": "sha512-VcOCbqv3MxzMZdH6jyqpzsfyNV0cG5F4TKXnnXcJ/QVQcWsN2BU6NrCiwkZHKEjbOYbxwBTdBHq1gnR5qz4baw==", + "license": "MIT", "dependencies": { "@griffel/react": "^1.0.0", "tslib": "^2.1.0" @@ -829,13 +869,14 @@ } }, "node_modules/@fluentui/react-icons-mdl2": { - "version": "1.3.59", - "resolved": "https://registry.npmjs.org/@fluentui/react-icons-mdl2/-/react-icons-mdl2-1.3.59.tgz", - "integrity": "sha512-cdvdvUXCrGHEob+3GWd8FJehbrxSXYliXkEwKVDOxC2+vBE6z1XanIRloGwdHBLnBsps46eYvvRVBHGlCUxmwg==", + "version": "1.3.72", + "resolved": "https://registry.npmjs.org/@fluentui/react-icons-mdl2/-/react-icons-mdl2-1.3.72.tgz", + "integrity": "sha512-uuBPsv1ep9EK92IwsmXPkalYQi0d5TNiReIbreHBeoHaGzyCRlXqwAW0XXqVP5/iCW1EyozwfccFovR6vyXoXw==", + "license": "MIT", "dependencies": { - "@fluentui/react-icon-provider": "^1.3.55", - "@fluentui/set-version": "^8.2.14", - "@fluentui/utilities": "^8.15.0", + "@fluentui/react-icon-provider": "^1.3.68", + "@fluentui/set-version": "^8.2.23", + "@fluentui/utilities": "^8.15.13", "@microsoft/load-themed-styles": "^1.10.26", "tslib": "^2.1.0" }, @@ -844,12 +885,13 @@ } }, "node_modules/@fluentui/react-icons-mdl2/node_modules/@fluentui/react-icon-provider": { - "version": "1.3.55", - "resolved": "https://registry.npmjs.org/@fluentui/react-icon-provider/-/react-icon-provider-1.3.55.tgz", - "integrity": "sha512-fa7AO7T+7+c5K6lIcsHbaC0apEf3LSQclK2F/PWMyn1r1mQp4eHIMxAPFxXrl/ay8QxRTaRhzIgxgar/XTXRjw==", + "version": "1.3.68", + "resolved": "https://registry.npmjs.org/@fluentui/react-icon-provider/-/react-icon-provider-1.3.68.tgz", + "integrity": "sha512-Mhxx8p+p0h0bN4gIoo+jGQ9jDBhAGc3HuRcS0CD9cld9eVGB/hr/RIiBj+39JQGJqzRB4rntLnLPFvjRAjPWiA==", + "license": "MIT", "dependencies": { - "@fluentui/set-version": "^8.2.14", - "@fluentui/style-utilities": "^8.10.5", + "@fluentui/set-version": "^8.2.23", + "@fluentui/style-utilities": "^8.10.18", "tslib": "^2.1.0" }, "peerDependencies": { @@ -860,15 +902,16 @@ } }, "node_modules/@fluentui/react-image": { - "version": "9.1.62", - "resolved": "https://registry.npmjs.org/@fluentui/react-image/-/react-image-9.1.62.tgz", - "integrity": "sha512-j8V9XWdl9otn1kfBqo5EGBD7nvvaabb9H3Wz8I0pMfeC8fMwq6iR8KYO+MbFUSwmekMEoqsP8qPKHUOViMEhPw==", + "version": "9.1.71", + "resolved": "https://registry.npmjs.org/@fluentui/react-image/-/react-image-9.1.71.tgz", + "integrity": "sha512-OKUbVsZLVe/kRP7KrweHDkztY8FUz6HzEPxZvJqIkwDf+KIJJxDV4R9Hpw8Fis6ceLbj/VI5JUVwGIp5YQlsjA==", + "license": "MIT", "dependencies": { - "@fluentui/react-jsx-runtime": "^9.0.34", - "@fluentui/react-shared-contexts": "^9.15.2", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-shared-contexts": "^9.20.0", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -879,17 +922,18 @@ } }, "node_modules/@fluentui/react-infobutton": { - "version": "9.0.0-beta.98", - "resolved": "https://registry.npmjs.org/@fluentui/react-infobutton/-/react-infobutton-9.0.0-beta.98.tgz", - "integrity": "sha512-7IFrKpmv1PnTN7ZrisYE7qrsfY6bRTK5AVnsQVrBX9/6xkLe4ZE52cQtoAnTX1gMIgqDhgoOd/RTzTO07xxPiw==", - "dependencies": { - "@fluentui/react-icons": "^2.0.224", - "@fluentui/react-jsx-runtime": "^9.0.34", - "@fluentui/react-label": "^9.1.66", - "@fluentui/react-popover": "^9.9.2", - "@fluentui/react-tabster": "^9.19.5", + "version": "9.0.0-beta.102", + "resolved": "https://registry.npmjs.org/@fluentui/react-infobutton/-/react-infobutton-9.0.0-beta.102.tgz", + "integrity": "sha512-3kA4F0Vga8Ds6JGlBajLCCDOo/LmPuS786Wg7ui4ZTDYVIMzy1yp2XuVcZniifBFvEp0HQCUoDPWUV0VI3FfzQ==", + "license": "MIT", + "dependencies": { + "@fluentui/react-icons": "^2.0.237", + "@fluentui/react-jsx-runtime": "^9.0.36", + "@fluentui/react-label": "^9.1.68", + "@fluentui/react-popover": "^9.9.6", + "@fluentui/react-tabster": "^9.21.0", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", + "@fluentui/react-utilities": "^9.18.7", "@griffel/react": "^1.5.14", "@swc/helpers": "^0.5.1" }, @@ -901,38 +945,40 @@ } }, "node_modules/@fluentui/react-infolabel": { - "version": "9.0.26", - "resolved": "https://registry.npmjs.org/@fluentui/react-infolabel/-/react-infolabel-9.0.26.tgz", - "integrity": "sha512-mvLRsiS0bP0mLokMmU8Aho8Wea4OE1vBvisuC2uwq584WyDyk8rxjyPNqFo0BrcgwHSB++bHcQpsF4keP1UVQQ==", - "dependencies": { - "@fluentui/react-icons": "^2.0.224", - "@fluentui/react-jsx-runtime": "^9.0.34", - "@fluentui/react-label": "^9.1.66", - "@fluentui/react-popover": "^9.9.2", - "@fluentui/react-tabster": "^9.19.5", + "version": "9.0.40", + "resolved": "https://registry.npmjs.org/@fluentui/react-infolabel/-/react-infolabel-9.0.40.tgz", + "integrity": "sha512-3hXmmiuv5gEqZWTNK25tp/YdLoIgIZ64XgFXDgtaRjK94evzEIYR2jRhhfQUWZorMOhTqqSBMfKK/+M8dwfk2g==", + "license": "MIT", + "dependencies": { + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-label": "^9.1.74", + "@fluentui/react-popover": "^9.9.15", + "@fluentui/react-tabster": "^9.22.3", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1" }, "peerDependencies": { "@types/react": ">=16.8.0 <19.0.0", "@types/react-dom": ">=16.8.0 <19.0.0", - "react": ">=16.8.0 <19.0.0", + "react": ">=16.14.0 <19.0.0", "react-dom": ">=16.8.0 <19.0.0" } }, "node_modules/@fluentui/react-input": { - "version": "9.4.69", - "resolved": "https://registry.npmjs.org/@fluentui/react-input/-/react-input-9.4.69.tgz", - "integrity": "sha512-pWpZYXZM0B/LbhpsboRdjm4hMRuoJMb6Kh2PcmNnPy1XGNkD4BASF5m9N8rgdZSgWyYxpCxO/ONGSqRY7A1O7Q==", + "version": "9.4.83", + "resolved": "https://registry.npmjs.org/@fluentui/react-input/-/react-input-9.4.83.tgz", + "integrity": "sha512-mM2aU0Xsqfx7LKFs1X0VBCyXncxRQBRHwEh+1MTvkbhgOYLxD3f2qEy/XNk0vyJ2rya/A2zHE9vwDHKn7Ck44A==", + "license": "MIT", "dependencies": { - "@fluentui/react-field": "^9.1.59", - "@fluentui/react-jsx-runtime": "^9.0.34", - "@fluentui/react-shared-contexts": "^9.15.2", + "@fluentui/react-field": "^9.1.71", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-shared-contexts": "^9.20.0", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -943,11 +989,12 @@ } }, "node_modules/@fluentui/react-jsx-runtime": { - "version": "9.0.34", - "resolved": "https://registry.npmjs.org/@fluentui/react-jsx-runtime/-/react-jsx-runtime-9.0.34.tgz", - "integrity": "sha512-pJ/f/xZ6+19sD3kjyMp2NDmIwexdMbYHeqmr/AgbI+G3Fb2NKA0UA6XylAXlCiAx4nEXdOETJDrrDsdFAV+/Fw==", + "version": "9.0.42", + "resolved": "https://registry.npmjs.org/@fluentui/react-jsx-runtime/-/react-jsx-runtime-9.0.42.tgz", + "integrity": "sha512-/iKhJx5htz+iBjWRjviM7cEAiG0y9oqVimmeWaZChi0M5abwvHkueYQ+n1BhzYOtsdeuXXcbrkx4VkaO5j5Efg==", + "license": "MIT", "dependencies": { - "@fluentui/react-utilities": "^9.18.5", + "@fluentui/react-utilities": "^9.18.13", "@swc/helpers": "^0.5.1", "react-is": "^17.0.2" }, @@ -959,18 +1006,20 @@ "node_modules/@fluentui/react-jsx-runtime/node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "license": "MIT" }, "node_modules/@fluentui/react-label": { - "version": "9.1.66", - "resolved": "https://registry.npmjs.org/@fluentui/react-label/-/react-label-9.1.66.tgz", - "integrity": "sha512-N0HOD5Wd6NI3YG7nGIhRhrjNBfNpDyaWxNYGMVnQs0pa6CWXcT6sCVxXxxSYYEnVFIDX7JmzFc4mgombTwnmmg==", + "version": "9.1.74", + "resolved": "https://registry.npmjs.org/@fluentui/react-label/-/react-label-9.1.74.tgz", + "integrity": "sha512-9EDwomVwcHJvI7QKIsBok3EQ5Ty5R3cDMnYZl7OIugffEvt+UWcmNyIOckYt80vsPYNbM9XqTt4rNAvCkFd1UQ==", + "license": "MIT", "dependencies": { - "@fluentui/react-jsx-runtime": "^9.0.34", - "@fluentui/react-shared-contexts": "^9.15.2", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-shared-contexts": "^9.20.0", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -981,17 +1030,18 @@ } }, "node_modules/@fluentui/react-link": { - "version": "9.2.15", - "resolved": "https://registry.npmjs.org/@fluentui/react-link/-/react-link-9.2.15.tgz", - "integrity": "sha512-wZzLz3od22wJhmEd5xwOULVAuXXEdBRDa01mojtnU25pBhIErvY2VXU5QNS+Yycjt52NvBElB6Ut+LOKJ9KD2g==", + "version": "9.2.28", + "resolved": "https://registry.npmjs.org/@fluentui/react-link/-/react-link-9.2.28.tgz", + "integrity": "sha512-k1/i8ktTCbztK88YogIt2FYCpJJMFxC4IzXAvpKLioTw6N3ITmxo9KuNNMvOYckGgHyvJliWutu/rSozFXTDmg==", + "license": "MIT", "dependencies": { "@fluentui/keyboard-keys": "^9.0.7", - "@fluentui/react-jsx-runtime": "^9.0.34", - "@fluentui/react-shared-contexts": "^9.15.2", - "@fluentui/react-tabster": "^9.19.5", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-shared-contexts": "^9.20.0", + "@fluentui/react-tabster": "^9.22.3", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -1002,22 +1052,23 @@ } }, "node_modules/@fluentui/react-menu": { - "version": "9.13.5", - "resolved": "https://registry.npmjs.org/@fluentui/react-menu/-/react-menu-9.13.5.tgz", - "integrity": "sha512-P9y31r7g/YAlL6zPWTFM26rp1gP6jkIqaXVCB6OuV/ZMdAxGIUcILww4d5rGr6G/tUpA9fL9trpWCgQpHSOXKw==", + "version": "9.14.11", + "resolved": "https://registry.npmjs.org/@fluentui/react-menu/-/react-menu-9.14.11.tgz", + "integrity": "sha512-E1LWDywVukrbmKN8GIbdmTxP3w3q9V7ig4xDJNIt3VQxNoHdDS/Dz00pT6SZT/zP9VqCnfhpuF0xu+q5sOh4Nw==", + "license": "MIT", "dependencies": { "@fluentui/keyboard-keys": "^9.0.7", - "@fluentui/react-aria": "^9.10.2", - "@fluentui/react-context-selector": "^9.1.56", - "@fluentui/react-icons": "^2.0.224", - "@fluentui/react-jsx-runtime": "^9.0.34", - "@fluentui/react-portal": "^9.4.18", - "@fluentui/react-positioning": "^9.14.2", - "@fluentui/react-shared-contexts": "^9.15.2", - "@fluentui/react-tabster": "^9.19.5", + "@fluentui/react-aria": "^9.13.2", + "@fluentui/react-context-selector": "^9.1.65", + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-portal": "^9.4.31", + "@fluentui/react-positioning": "^9.15.6", + "@fluentui/react-shared-contexts": "^9.20.0", + "@fluentui/react-tabster": "^9.22.3", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -1028,60 +1079,81 @@ } }, "node_modules/@fluentui/react-message-bar": { - "version": "9.0.24", - "resolved": "https://registry.npmjs.org/@fluentui/react-message-bar/-/react-message-bar-9.0.24.tgz", - "integrity": "sha512-gcqXRMFDbPc23aDTovwVUepufUJjjtQlnfqkWEwjlV6k1UdAfIzqgSThm81ztFXop1StyOH8gVj7QBGpBDz7+g==", - "dependencies": { - "@fluentui/react-button": "^9.3.73", - "@fluentui/react-icons": "^2.0.224", - "@fluentui/react-jsx-runtime": "^9.0.34", - "@fluentui/react-shared-contexts": "^9.15.2", + "version": "9.2.6", + "resolved": "https://registry.npmjs.org/@fluentui/react-message-bar/-/react-message-bar-9.2.6.tgz", + "integrity": "sha512-A1OwuzCK9Rce4PHYyPnB56qVMj2bg9Qb9h2qpHrChhz6Giol5Ty45lmmVqEpd/w+lyMC73Sgqyfg2Lucy/p3vw==", + "license": "MIT", + "dependencies": { + "@fluentui/react-button": "^9.3.87", + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-shared-contexts": "^9.20.0", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1", "react-transition-group": "^4.4.1" }, "peerDependencies": { "@types/react": ">=16.8.0 <19.0.0", "@types/react-dom": ">=16.8.0 <19.0.0", - "react": ">=16.8.0 <19.0.0", + "react": ">=16.14.0 <19.0.0", "react-dom": ">=16.8.0 <19.0.0" } }, "node_modules/@fluentui/react-migration-v8-v9": { - "version": "9.6.3", - "resolved": "https://registry.npmjs.org/@fluentui/react-migration-v8-v9/-/react-migration-v8-v9-9.6.3.tgz", - "integrity": "sha512-JTyBPUh5uy9R1B/KVVZ5HSOJThodZxyO3nvNlzUWFE3RUWRDb/IHOw+FIyEEybtb4QAcqErSGhdokn9lxiNF3A==", + "version": "9.6.22", + "resolved": "https://registry.npmjs.org/@fluentui/react-migration-v8-v9/-/react-migration-v8-v9-9.6.22.tgz", + "integrity": "sha512-uq6hFIovsmUVQLcvkUxzoyJu4UXIN169rq+778Fl+AzoznVGgXDx3FoR8sDTtJB5fZvQBqQR7y2vilRQdA8Wxw==", + "license": "MIT", "dependencies": { "@ctrl/tinycolor": "3.3.4", - "@fluentui/fluent2-theme": "^8.107.68", - "@fluentui/react": "^8.117.0", - "@fluentui/react-components": "^9.47.2", - "@fluentui/react-hooks": "^8.7.0", - "@fluentui/react-icons": "^2.0.224", + "@fluentui/fluent2-theme": "^8.107.91", + "@fluentui/react": "^8.119.3", + "@fluentui/react-components": "^9.54.6", + "@fluentui/react-hooks": "^8.8.10", + "@fluentui/react-icons": "^2.0.245", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1" }, "peerDependencies": { "@types/react": ">=16.14.0 <19.0.0", "@types/react-dom": ">=16.9.0 <19.0.0", - "react": ">=16.14.0 <19.0.0", + "react": ">=16.8.0 <19.0.0", "react-dom": ">=16.14.0 <19.0.0" } }, + "node_modules/@fluentui/react-motion": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/@fluentui/react-motion/-/react-motion-9.4.0.tgz", + "integrity": "sha512-WeLA0/INSf74DKFjCxHjn16p0Pak7LnKlaxva8r5ZMNNQ2Mcl7dFGdY+2e+qejghR7+8fzeojr+nQfKXH94uVQ==", + "license": "MIT", + "dependencies": { + "@fluentui/react-shared-contexts": "^9.20.0", + "@fluentui/react-utilities": "^9.18.13", + "@swc/helpers": "^0.5.1", + "react-is": "^17.0.2" + }, + "peerDependencies": { + "@types/react": ">=16.8.0 <19.0.0", + "@types/react-dom": ">=16.8.0 <19.0.0", + "react": ">=16.14.0 <19.0.0", + "react-dom": ">=16.8.0 <19.0.0" + } + }, "node_modules/@fluentui/react-motion-preview": { - "version": "0.5.17", - "resolved": "https://registry.npmjs.org/@fluentui/react-motion-preview/-/react-motion-preview-0.5.17.tgz", - "integrity": "sha512-7hnFuCpF7el6eQq3xwMKOWUWhfY0/UjHhaKkJ3NWHQuM8H7mzYPTifpphSUvh4DYA5XEUh8n8YEvnNy1kFHsVg==", + "version": "0.5.25", + "resolved": "https://registry.npmjs.org/@fluentui/react-motion-preview/-/react-motion-preview-0.5.25.tgz", + "integrity": "sha512-TRmDFTuIEOeCs5HCyk5NCpMXBgY8L7tSkjAyZVdZtBY5KxFGlMys+7DnXTXJNDud5OvgDphU6XEvKEGFmaq/Uw==", + "license": "MIT", "dependencies": { - "@fluentui/react-jsx-runtime": "^9.0.34", - "@fluentui/react-shared-contexts": "^9.15.2", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-shared-contexts": "^9.20.0", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -1091,16 +1163,23 @@ "react-dom": ">=16.14.0 <19.0.0" } }, + "node_modules/@fluentui/react-motion/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "license": "MIT" + }, "node_modules/@fluentui/react-overflow": { - "version": "9.1.15", - "resolved": "https://registry.npmjs.org/@fluentui/react-overflow/-/react-overflow-9.1.15.tgz", - "integrity": "sha512-oIHwP9jLP3vzUlPy2M8shzgwHSvIh3mhc2A5CPTyu+aU906NFV6EFEx03vy62Cof21Ux71KOpPTFTAX0tBQrAA==", + "version": "9.1.25", + "resolved": "https://registry.npmjs.org/@fluentui/react-overflow/-/react-overflow-9.1.25.tgz", + "integrity": "sha512-NfZF6D+5xlaUN9bJ5sziE5VkO0SHmJeoUXu+K0wXTpexVQVc16xh9Ob9wF4FouQ6JnD86WWdEGkALKpyaNQieQ==", + "license": "MIT", "dependencies": { - "@fluentui/priority-overflow": "^9.1.11", - "@fluentui/react-context-selector": "^9.1.56", + "@fluentui/priority-overflow": "^9.1.13", + "@fluentui/react-context-selector": "^9.1.65", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -1111,17 +1190,18 @@ } }, "node_modules/@fluentui/react-persona": { - "version": "9.2.78", - "resolved": "https://registry.npmjs.org/@fluentui/react-persona/-/react-persona-9.2.78.tgz", - "integrity": "sha512-pWpyTYtoV7y1vHZv/MMc+h6kbIh9jB69FMXjkNX2uUiEBq0e+RQlkDhivZv58t9y6S8ZqdPZEelJgbH8HfHekw==", - "dependencies": { - "@fluentui/react-avatar": "^9.6.19", - "@fluentui/react-badge": "^9.2.29", - "@fluentui/react-jsx-runtime": "^9.0.34", - "@fluentui/react-shared-contexts": "^9.15.2", + "version": "9.2.92", + "resolved": "https://registry.npmjs.org/@fluentui/react-persona/-/react-persona-9.2.92.tgz", + "integrity": "sha512-TNbCR0NY4JIM217DZlSZBaCFkbFPzd6KRuVwC9XGLhHhTtA+Wr3J86OgWldtjTAUWcX6eJ94JsW0aYT4exCbYA==", + "license": "MIT", + "dependencies": { + "@fluentui/react-avatar": "^9.6.33", + "@fluentui/react-badge": "^9.2.41", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-shared-contexts": "^9.20.0", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -1132,21 +1212,22 @@ } }, "node_modules/@fluentui/react-popover": { - "version": "9.9.2", - "resolved": "https://registry.npmjs.org/@fluentui/react-popover/-/react-popover-9.9.2.tgz", - "integrity": "sha512-F/7VTPZMVCY/dwqumzrp+wzRNTlsKJ9Gz1nmZPZuO7IMBC8XRIGkjqdjW7oW8SzIrRmOTkAvmsn4UfPL19spiw==", + "version": "9.9.15", + "resolved": "https://registry.npmjs.org/@fluentui/react-popover/-/react-popover-9.9.15.tgz", + "integrity": "sha512-9KVRpKa1IDL6RGOJF7CYZvC+ayhye65fTshRYz17kiSXPHX3oshrPS+TwsrQq6ENmOuoirVva7AA6AqyooDrjQ==", + "license": "MIT", "dependencies": { "@fluentui/keyboard-keys": "^9.0.7", - "@fluentui/react-aria": "^9.10.2", - "@fluentui/react-context-selector": "^9.1.56", - "@fluentui/react-jsx-runtime": "^9.0.34", - "@fluentui/react-portal": "^9.4.18", - "@fluentui/react-positioning": "^9.14.2", - "@fluentui/react-shared-contexts": "^9.15.2", - "@fluentui/react-tabster": "^9.19.5", + "@fluentui/react-aria": "^9.13.2", + "@fluentui/react-context-selector": "^9.1.65", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-portal": "^9.4.31", + "@fluentui/react-positioning": "^9.15.6", + "@fluentui/react-shared-contexts": "^9.20.0", + "@fluentui/react-tabster": "^9.22.3", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -1157,14 +1238,15 @@ } }, "node_modules/@fluentui/react-portal": { - "version": "9.4.18", - "resolved": "https://registry.npmjs.org/@fluentui/react-portal/-/react-portal-9.4.18.tgz", - "integrity": "sha512-ShWpbZ2vjA/8yrk34e2n8+B+w034reYaxxfSq9N8csNsMbTInKdn44wTPp1ikcuqzZFJlkVFW4+LbKeQ/DvtZQ==", + "version": "9.4.31", + "resolved": "https://registry.npmjs.org/@fluentui/react-portal/-/react-portal-9.4.31.tgz", + "integrity": "sha512-c/mrsn29MvBIxkyYrIAdI9E9JCMicF7mCxpWFbQQZMYKdu4/qLCtkmQfvQKEI9WFQzLS8IzTRM+NajNewUi/nA==", + "license": "MIT", "dependencies": { - "@fluentui/react-shared-contexts": "^9.15.2", - "@fluentui/react-tabster": "^9.19.5", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", + "@fluentui/react-shared-contexts": "^9.20.0", + "@fluentui/react-tabster": "^9.22.3", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1", "use-disposable": "^1.0.1" }, @@ -1176,9 +1258,10 @@ } }, "node_modules/@fluentui/react-portal-compat-context": { - "version": "9.0.11", - "resolved": "https://registry.npmjs.org/@fluentui/react-portal-compat-context/-/react-portal-compat-context-9.0.11.tgz", - "integrity": "sha512-ubvW/ej0O+Pago9GH3mPaxzUgsNnBoqvghNamWjyKvZIViyaXUG6+sgcAl721R+qGAFac+A20akI5qDJz/xtdg==", + "version": "9.0.12", + "resolved": "https://registry.npmjs.org/@fluentui/react-portal-compat-context/-/react-portal-compat-context-9.0.12.tgz", + "integrity": "sha512-5AVXWX9GnbvwnJZYUb4LSIF7BsI/N8oTI6+7Yn0w6B3yaWykA8Menlz757X5tgVBjouEj4Eom+AoVvA7u8gPDA==", + "license": "MIT", "dependencies": { "@swc/helpers": "^0.5.1" }, @@ -1188,16 +1271,17 @@ } }, "node_modules/@fluentui/react-positioning": { - "version": "9.14.2", - "resolved": "https://registry.npmjs.org/@fluentui/react-positioning/-/react-positioning-9.14.2.tgz", - "integrity": "sha512-m0buzn3UI7j2WjCGL83YwC064Xe9N/dQJ8aSwhv/xXBgQkxHnHYAs3hLG4Tjb/tliEOobntFlSI7O1NYKiDrFw==", + "version": "9.15.6", + "resolved": "https://registry.npmjs.org/@fluentui/react-positioning/-/react-positioning-9.15.6.tgz", + "integrity": "sha512-733OgnMAJt9OQ7yvjsshNR+mWtQE7+236HM6gf1SSL8D9j1W481b+jxnXbo6qiPDkAn1Me5wdGZoJmEN5jFO1w==", + "license": "MIT", "dependencies": { "@floating-ui/devtools": "0.2.1", "@floating-ui/dom": "^1.2.0", - "@fluentui/react-shared-contexts": "^9.15.2", + "@fluentui/react-shared-contexts": "^9.20.0", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -1208,16 +1292,17 @@ } }, "node_modules/@fluentui/react-progress": { - "version": "9.1.69", - "resolved": "https://registry.npmjs.org/@fluentui/react-progress/-/react-progress-9.1.69.tgz", - "integrity": "sha512-aoo9hAFPafO0by5Ibv9EaFyDZZSBH1Gy3udU3lKAJMTrny5kgzXhOnITvwUhM8Za1G2M5rX/y62ujcYAsQgLdw==", + "version": "9.1.82", + "resolved": "https://registry.npmjs.org/@fluentui/react-progress/-/react-progress-9.1.82.tgz", + "integrity": "sha512-r4aZ57rEoReNZoIDG65OvXVk5/19HZGLFghBQ6J8ytLLnIYwqhOueUyv2HEcRXpK16ayhN37GpQE7eBLnMLGGg==", + "license": "MIT", "dependencies": { - "@fluentui/react-field": "^9.1.59", - "@fluentui/react-jsx-runtime": "^9.0.34", - "@fluentui/react-shared-contexts": "^9.15.2", + "@fluentui/react-field": "^9.1.71", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-shared-contexts": "^9.20.0", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -1228,18 +1313,19 @@ } }, "node_modules/@fluentui/react-provider": { - "version": "9.13.16", - "resolved": "https://registry.npmjs.org/@fluentui/react-provider/-/react-provider-9.13.16.tgz", - "integrity": "sha512-LHiy/4wefxgx+dneWLCrvTgC3qP2kHm7M1tnx2jXKZsBwpXMhAWqxBN3xs1y+u0fyI3RqhJpJAOmKLtmHW2/Og==", - "dependencies": { - "@fluentui/react-icons": "^2.0.224", - "@fluentui/react-jsx-runtime": "^9.0.34", - "@fluentui/react-shared-contexts": "^9.15.2", - "@fluentui/react-tabster": "^9.19.5", + "version": "9.17.0", + "resolved": "https://registry.npmjs.org/@fluentui/react-provider/-/react-provider-9.17.0.tgz", + "integrity": "sha512-z2nKk8MEDmDivxhcySLhD4xuEuSJgXjO14SjMcLnerAimm3Wftgoayga23cvwGgLmoeqAjG7ykEgLEfAzRDk7A==", + "license": "MIT", + "dependencies": { + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-shared-contexts": "^9.20.0", + "@fluentui/react-tabster": "^9.22.3", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/core": "^1.14.1", - "@griffel/react": "^1.5.14", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/core": "^1.16.0", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -1250,18 +1336,19 @@ } }, "node_modules/@fluentui/react-radio": { - "version": "9.2.13", - "resolved": "https://registry.npmjs.org/@fluentui/react-radio/-/react-radio-9.2.13.tgz", - "integrity": "sha512-tK26OntGs3FiUoMxCt4tnCf17am74KGXolRIDUDiyZKLICQq0xDlfokupCU9qGl59OUgMJGa/ZK4hijxZN3pyA==", - "dependencies": { - "@fluentui/react-field": "^9.1.59", - "@fluentui/react-jsx-runtime": "^9.0.34", - "@fluentui/react-label": "^9.1.66", - "@fluentui/react-shared-contexts": "^9.15.2", - "@fluentui/react-tabster": "^9.19.5", + "version": "9.2.27", + "resolved": "https://registry.npmjs.org/@fluentui/react-radio/-/react-radio-9.2.27.tgz", + "integrity": "sha512-CNGloT6Jc11I8fMpn9pqTgpN2gImgNuNY5YulOUQZ8Z4afT1P0YCWDTY6kRIUo3LFH4x4A1YsN3O05Sn2dImcA==", + "license": "MIT", + "dependencies": { + "@fluentui/react-field": "^9.1.71", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-label": "^9.1.74", + "@fluentui/react-shared-contexts": "^9.20.0", + "@fluentui/react-tabster": "^9.22.3", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -1272,37 +1359,60 @@ } }, "node_modules/@fluentui/react-rating": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@fluentui/react-rating/-/react-rating-9.0.1.tgz", - "integrity": "sha512-YNAv95ZnvJPI9Vt7OVmYfEqib4ijqVlXEz/cdVKwQG9s2Qek7jQwrH5lwQ0lFduLfli6Vu5ukxFPettMPp/mLQ==", + "version": "9.0.15", + "resolved": "https://registry.npmjs.org/@fluentui/react-rating/-/react-rating-9.0.15.tgz", + "integrity": "sha512-fa8rDyvxQplqL234IMThWRVfVj3U7y3LiJcjliZ1YrTNkTVRg/w7LK010625Ased2IOBIWS4YLP1UFBFdxIflw==", + "license": "MIT", "dependencies": { - "@fluentui/react-icons": "^2.0.224", - "@fluentui/react-jsx-runtime": "^9.0.34", - "@fluentui/react-tabster": "^9.19.5", + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-tabster": "^9.22.3", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1" }, "peerDependencies": { "@types/react": ">=16.8.0 <19.0.0", "@types/react-dom": ">=16.8.0 <19.0.0", - "react": ">=16.8.0 <19.0.0", + "react": ">=16.14.0 <19.0.0", "react-dom": ">=16.8.0 <19.0.0" } }, + "node_modules/@fluentui/react-search": { + "version": "9.0.12", + "resolved": "https://registry.npmjs.org/@fluentui/react-search/-/react-search-9.0.12.tgz", + "integrity": "sha512-63Yxkx0JeQ8ueSr37tc/OA+yYQFPQHAoVmvJyqhYngRLJUzuGT3ErvM6WjBtZjQ49ErRXjmwJw9Rcf3HowUZCg==", + "license": "MIT", + "dependencies": { + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-input": "^9.4.83", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-theme": "^9.1.19", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <19.0.0", + "@types/react-dom": ">=16.9.0 <19.0.0", + "react": ">=16.14.0 <19.0.0", + "react-dom": ">=16.14.0 <19.0.0" + } + }, "node_modules/@fluentui/react-select": { - "version": "9.1.69", - "resolved": "https://registry.npmjs.org/@fluentui/react-select/-/react-select-9.1.69.tgz", - "integrity": "sha512-Ypmq0Ge6nlx2FmVaB1IKlWCjkTsaIAiLlKHoyjQg/QNvcE/RUA02ErSY4iliy7UOrIqZf2hX2tTh0lbDKb0Ufg==", - "dependencies": { - "@fluentui/react-field": "^9.1.59", - "@fluentui/react-icons": "^2.0.224", - "@fluentui/react-jsx-runtime": "^9.0.34", - "@fluentui/react-shared-contexts": "^9.15.2", + "version": "9.1.82", + "resolved": "https://registry.npmjs.org/@fluentui/react-select/-/react-select-9.1.82.tgz", + "integrity": "sha512-anu9A918H13HRRFdnmWk2XrjiVkefreUKsY5gA07qK94TJRH/drw1GPCOX5fOkQF60J7fqZShyNVTqE4fJ+9pQ==", + "license": "MIT", + "dependencies": { + "@fluentui/react-field": "^9.1.71", + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-shared-contexts": "^9.20.0", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -1313,9 +1423,10 @@ } }, "node_modules/@fluentui/react-shared-contexts": { - "version": "9.15.2", - "resolved": "https://registry.npmjs.org/@fluentui/react-shared-contexts/-/react-shared-contexts-9.15.2.tgz", - "integrity": "sha512-0KEYEYGP4pjMrxZ5EytYqkUe56+tlr46ltxyKdcPcbfN+ptPffC9cevAR+4VIcb4xgmW+c7JT6nxDr5Rd5pvcw==", + "version": "9.20.0", + "resolved": "https://registry.npmjs.org/@fluentui/react-shared-contexts/-/react-shared-contexts-9.20.0.tgz", + "integrity": "sha512-LOMgP51dC/dOQOopEhvRk9V/GlpkStMbXTsci+2raG+Zno3eIdS3TesWCango+r5rpBFCIZl4HOpGEErHGm03Q==", + "license": "MIT", "dependencies": { "@fluentui/react-theme": "^9.1.19", "@swc/helpers": "^0.5.1" @@ -1326,16 +1437,17 @@ } }, "node_modules/@fluentui/react-skeleton": { - "version": "9.0.57", - "resolved": "https://registry.npmjs.org/@fluentui/react-skeleton/-/react-skeleton-9.0.57.tgz", - "integrity": "sha512-YBJBFX/RX4GBeMdP3jXzid50tFC+PSdfoBiwVTeSuZzjxxhLrUQpyFErhkZrnE+W53G70/7KbZpRqbOCVjW7YQ==", + "version": "9.1.10", + "resolved": "https://registry.npmjs.org/@fluentui/react-skeleton/-/react-skeleton-9.1.10.tgz", + "integrity": "sha512-W0OkdaS7+5NkK32WTTP1sbBWu+NBL343YYU0R52LHfXBMJXlhJ/pNIcfIPrBSij6szmMiRZrPzYGtDzFx/S4qA==", + "license": "MIT", "dependencies": { - "@fluentui/react-field": "^9.1.59", - "@fluentui/react-jsx-runtime": "^9.0.34", - "@fluentui/react-shared-contexts": "^9.15.2", + "@fluentui/react-field": "^9.1.71", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-shared-contexts": "^9.20.0", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -1346,17 +1458,18 @@ } }, "node_modules/@fluentui/react-slider": { - "version": "9.1.75", - "resolved": "https://registry.npmjs.org/@fluentui/react-slider/-/react-slider-9.1.75.tgz", - "integrity": "sha512-mLwPOFhsO2bgZZCSdBUXCd30fWp9bHvROZaiTrn+kslY2bqHAQQfWox4YTNoAQrWl0SBYY8UHca8YTcCWmMtXA==", - "dependencies": { - "@fluentui/react-field": "^9.1.59", - "@fluentui/react-jsx-runtime": "^9.0.34", - "@fluentui/react-shared-contexts": "^9.15.2", - "@fluentui/react-tabster": "^9.19.5", + "version": "9.1.89", + "resolved": "https://registry.npmjs.org/@fluentui/react-slider/-/react-slider-9.1.89.tgz", + "integrity": "sha512-cKCGAGVbKK6hYIQKfnveQehfCEr75gKPUSP9B7HdQX8CLiPUgUSrwXGouSYgJSLcpD3BN7boLj7DUfs3mhxMqQ==", + "license": "MIT", + "dependencies": { + "@fluentui/react-field": "^9.1.71", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-shared-contexts": "^9.20.0", + "@fluentui/react-tabster": "^9.22.3", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -1367,18 +1480,19 @@ } }, "node_modules/@fluentui/react-spinbutton": { - "version": "9.2.69", - "resolved": "https://registry.npmjs.org/@fluentui/react-spinbutton/-/react-spinbutton-9.2.69.tgz", - "integrity": "sha512-/KGoIEuVQmA/4groKY57rgBk+k2er3N7e/FljD6S2HzCW5WhBIkE4icYz0kJuA42c9bfYBd2phdkt2/6KlZCFw==", + "version": "9.2.83", + "resolved": "https://registry.npmjs.org/@fluentui/react-spinbutton/-/react-spinbutton-9.2.83.tgz", + "integrity": "sha512-wWdggoxT0nUnbE8PqnUojnH0OznnkUvHHINwE0l3II8EDz0YXTPBZMoaRfB84+FykdDAfyNm2BWa+uEtRH47ZA==", + "license": "MIT", "dependencies": { "@fluentui/keyboard-keys": "^9.0.7", - "@fluentui/react-field": "^9.1.59", - "@fluentui/react-icons": "^2.0.224", - "@fluentui/react-jsx-runtime": "^9.0.34", - "@fluentui/react-shared-contexts": "^9.15.2", + "@fluentui/react-field": "^9.1.71", + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-shared-contexts": "^9.20.0", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -1389,16 +1503,17 @@ } }, "node_modules/@fluentui/react-spinner": { - "version": "9.4.2", - "resolved": "https://registry.npmjs.org/@fluentui/react-spinner/-/react-spinner-9.4.2.tgz", - "integrity": "sha512-fdxB+6FNM1qWNuzAEBGpF+u8esW7KyuVYujdVlIN/7uKRbwWe8sp4UMe7aHuvRtYleG9i1pMYnO3nwmrXYA6IQ==", + "version": "9.4.12", + "resolved": "https://registry.npmjs.org/@fluentui/react-spinner/-/react-spinner-9.4.12.tgz", + "integrity": "sha512-pcIxhJtM0SFqDqL7/FUm9h2AbKn9D7WLjwXfE0Umx8T9cp8H/D1VgL9whCGqnvwa2BX2xWTmPbSz6QVRdwL7Jw==", + "license": "MIT", "dependencies": { - "@fluentui/react-jsx-runtime": "^9.0.34", - "@fluentui/react-label": "^9.1.66", - "@fluentui/react-shared-contexts": "^9.15.2", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-label": "^9.1.74", + "@fluentui/react-shared-contexts": "^9.20.0", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -1408,20 +1523,44 @@ "react-dom": ">=16.14.0 <19.0.0" } }, + "node_modules/@fluentui/react-swatch-picker": { + "version": "9.1.6", + "resolved": "https://registry.npmjs.org/@fluentui/react-swatch-picker/-/react-swatch-picker-9.1.6.tgz", + "integrity": "sha512-vLpmM2ENFPs+4qemjswfO5gEveUFIqSS/Gu1z7FiBbp/tUNS13rXYQBiUPFCS8xEsQG4gNcozBs9Lejdyean4g==", + "license": "MIT", + "dependencies": { + "@fluentui/react-context-selector": "^9.1.65", + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-shared-contexts": "^9.20.0", + "@fluentui/react-tabster": "^9.22.3", + "@fluentui/react-theme": "^9.1.19", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", + "@swc/helpers": "^0.5.1" + }, + "peerDependencies": { + "@types/react": ">=16.8.0 <19.0.0", + "@types/react-dom": ">=16.8.0 <19.0.0", + "react": ">=16.14.0 <19.0.0", + "react-dom": ">=16.8.0 <19.0.0" + } + }, "node_modules/@fluentui/react-switch": { - "version": "9.1.75", - "resolved": "https://registry.npmjs.org/@fluentui/react-switch/-/react-switch-9.1.75.tgz", - "integrity": "sha512-tK6SD6mCYHgXtnxFW64kkmMqD9jIdnk49g34E/OMyXi4DLezDv+PFmYaiFXeV6ASoz5OZlt/3J3w7zRC0RhBog==", - "dependencies": { - "@fluentui/react-field": "^9.1.59", - "@fluentui/react-icons": "^2.0.224", - "@fluentui/react-jsx-runtime": "^9.0.34", - "@fluentui/react-label": "^9.1.66", - "@fluentui/react-shared-contexts": "^9.15.2", - "@fluentui/react-tabster": "^9.19.5", + "version": "9.1.89", + "resolved": "https://registry.npmjs.org/@fluentui/react-switch/-/react-switch-9.1.89.tgz", + "integrity": "sha512-9gYOhubFq5+4c2bnD6VJGfRCwl9X5RFp3AXztfrYELRTgwSsIeCAoa6VM3bu9nUi3V60GHCNHk38j8lVwDgy/w==", + "license": "MIT", + "dependencies": { + "@fluentui/react-field": "^9.1.71", + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-label": "^9.1.74", + "@fluentui/react-shared-contexts": "^9.20.0", + "@fluentui/react-tabster": "^9.22.3", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -1432,23 +1571,24 @@ } }, "node_modules/@fluentui/react-table": { - "version": "9.12.0", - "resolved": "https://registry.npmjs.org/@fluentui/react-table/-/react-table-9.12.0.tgz", - "integrity": "sha512-8qvN2hrYYtVXkVrB5+DMWNTIJogzWr0KUkFuvUZ9f1ylmvR8NcRQpxlU5q0yLaNKecmTLDoADWYF6Ldoyt5e2Q==", + "version": "9.15.11", + "resolved": "https://registry.npmjs.org/@fluentui/react-table/-/react-table-9.15.11.tgz", + "integrity": "sha512-6eK8v22lbZpMnBPVEwf7qaaueSrrVcLSMs9U4f3nNGo2pJVOnOcg9Vhvom/GL2D8FItpOOcTwxT7Asg/FGRIIA==", + "license": "MIT", "dependencies": { "@fluentui/keyboard-keys": "^9.0.7", - "@fluentui/react-aria": "^9.10.2", - "@fluentui/react-avatar": "^9.6.19", - "@fluentui/react-checkbox": "^9.2.18", - "@fluentui/react-context-selector": "^9.1.56", - "@fluentui/react-icons": "^2.0.224", - "@fluentui/react-jsx-runtime": "^9.0.34", - "@fluentui/react-radio": "^9.2.13", - "@fluentui/react-shared-contexts": "^9.15.2", - "@fluentui/react-tabster": "^9.19.5", + "@fluentui/react-aria": "^9.13.2", + "@fluentui/react-avatar": "^9.6.33", + "@fluentui/react-checkbox": "^9.2.32", + "@fluentui/react-context-selector": "^9.1.65", + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-radio": "^9.2.27", + "@fluentui/react-shared-contexts": "^9.20.0", + "@fluentui/react-tabster": "^9.22.3", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -1459,17 +1599,18 @@ } }, "node_modules/@fluentui/react-tabs": { - "version": "9.4.14", - "resolved": "https://registry.npmjs.org/@fluentui/react-tabs/-/react-tabs-9.4.14.tgz", - "integrity": "sha512-hXcgzQCnmHym5ERlitE1gWU974TT644034FUXoc4x4EoduLQ1FEebHRFZKajGeR+/gGHvBXXnbvdw6dNZwwJkw==", - "dependencies": { - "@fluentui/react-context-selector": "^9.1.56", - "@fluentui/react-jsx-runtime": "^9.0.34", - "@fluentui/react-shared-contexts": "^9.15.2", - "@fluentui/react-tabster": "^9.19.5", + "version": "9.4.27", + "resolved": "https://registry.npmjs.org/@fluentui/react-tabs/-/react-tabs-9.4.27.tgz", + "integrity": "sha512-aJmaTqXz77neyJpgffdKi9gpLtGQjRZcVcvhR/Ft0ZMC+u8XGkTlrtF0ntZZcnPwzeZ7P2qjMMrdvIyDsaSxMg==", + "license": "MIT", + "dependencies": { + "@fluentui/react-context-selector": "^9.1.65", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-shared-contexts": "^9.20.0", + "@fluentui/react-tabster": "^9.22.3", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -1480,17 +1621,48 @@ } }, "node_modules/@fluentui/react-tabster": { - "version": "9.19.5", - "resolved": "https://registry.npmjs.org/@fluentui/react-tabster/-/react-tabster-9.19.5.tgz", - "integrity": "sha512-bazFB5naT7/I8Q1+cRNvGhhlCQlWvLmCUpj+7tgMrfdX0ghRNI+adygsqKFx1oKkRm5ZBgsVFyk3M6AuDGoAQw==", + "version": "9.22.3", + "resolved": "https://registry.npmjs.org/@fluentui/react-tabster/-/react-tabster-9.22.3.tgz", + "integrity": "sha512-z/icX1sMTHVXJxq2tlNKGM9A9J7RYLgu03vVh+0z4N+Q4k5Oe0HvKFNyi447+R5UEHbWebMEpabbTIox3DuLQw==", + "license": "MIT", "dependencies": { - "@fluentui/react-shared-contexts": "^9.15.2", + "@fluentui/react-shared-contexts": "^9.20.0", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1", - "keyborg": "^2.5.0", - "tabster": "^6.0.1" + "keyborg": "^2.6.0", + "tabster": "^8.0.1" + }, + "peerDependencies": { + "@types/react": ">=16.14.0 <19.0.0", + "@types/react-dom": ">=16.9.0 <19.0.0", + "react": ">=16.14.0 <19.0.0", + "react-dom": ">=16.14.0 <19.0.0" + } + }, + "node_modules/@fluentui/react-tag-picker": { + "version": "9.2.3", + "resolved": "https://registry.npmjs.org/@fluentui/react-tag-picker/-/react-tag-picker-9.2.3.tgz", + "integrity": "sha512-5VvdPcFUY1PrvlLFDCfx1oOiwViukZiI9fQDeX2606wj3g+1lFmzao+8DLxjQ/jX9rEiLFrCrF0RIg+xcjRtxA==", + "license": "MIT", + "dependencies": { + "@fluentui/keyboard-keys": "^9.0.7", + "@fluentui/react-aria": "^9.13.2", + "@fluentui/react-combobox": "^9.13.2", + "@fluentui/react-context-selector": "^9.1.65", + "@fluentui/react-field": "^9.1.71", + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-portal": "^9.4.31", + "@fluentui/react-positioning": "^9.15.6", + "@fluentui/react-shared-contexts": "^9.20.0", + "@fluentui/react-tabster": "^9.22.3", + "@fluentui/react-tags": "^9.3.12", + "@fluentui/react-theme": "^9.1.19", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", + "@swc/helpers": "^0.5.1" }, "peerDependencies": { "@types/react": ">=16.14.0 <19.0.0", @@ -1500,20 +1672,21 @@ } }, "node_modules/@fluentui/react-tags": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/@fluentui/react-tags/-/react-tags-9.2.0.tgz", - "integrity": "sha512-eM5EQHeyCX+CI/mWQRIOE7U8t1L6eb77Ed2P90O+eP4rrk79HcZcsoUrPm8UcveYVGC4ebhLpF4k+qyDii27BQ==", + "version": "9.3.12", + "resolved": "https://registry.npmjs.org/@fluentui/react-tags/-/react-tags-9.3.12.tgz", + "integrity": "sha512-Qm77lq/lWoG0g7bOPZcvjJfyjqezI+uUTAYo1+5mugIhZ+7AyBzgsnCdrmdwn1yFgsSlNNes1+A78V/jHdUE0w==", + "license": "MIT", "dependencies": { "@fluentui/keyboard-keys": "^9.0.7", - "@fluentui/react-aria": "^9.10.2", - "@fluentui/react-avatar": "^9.6.19", - "@fluentui/react-icons": "^2.0.224", - "@fluentui/react-jsx-runtime": "^9.0.34", - "@fluentui/react-shared-contexts": "^9.15.2", - "@fluentui/react-tabster": "^9.19.5", + "@fluentui/react-aria": "^9.13.2", + "@fluentui/react-avatar": "^9.6.33", + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-shared-contexts": "^9.20.0", + "@fluentui/react-tabster": "^9.22.3", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -1523,16 +1696,44 @@ "react-dom": ">=16.14.0 <19.0.0" } }, + "node_modules/@fluentui/react-teaching-popover": { + "version": "9.1.11", + "resolved": "https://registry.npmjs.org/@fluentui/react-teaching-popover/-/react-teaching-popover-9.1.11.tgz", + "integrity": "sha512-qjA764EpNLHgYg41KplqDpKc4OuA0zsEZmomO256Q5MdLpaj6A5tPcpWb/cHb3XsdKHoOysbW4V3Fd9Gn1XBIQ==", + "license": "MIT", + "dependencies": { + "@fluentui/react-aria": "^9.13.2", + "@fluentui/react-button": "^9.3.87", + "@fluentui/react-context-selector": "^9.1.65", + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-popover": "^9.9.15", + "@fluentui/react-shared-contexts": "^9.20.0", + "@fluentui/react-tabster": "^9.22.3", + "@fluentui/react-theme": "^9.1.19", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", + "@swc/helpers": "^0.5.1", + "use-sync-external-store": "^1.2.0" + }, + "peerDependencies": { + "@types/react": ">=16.8.0 <19.0.0", + "@types/react-dom": ">=16.8.0 <19.0.0", + "react": ">=16.14.0 <19.0.0", + "react-dom": ">=16.8.0 <19.0.0" + } + }, "node_modules/@fluentui/react-text": { - "version": "9.4.14", - "resolved": "https://registry.npmjs.org/@fluentui/react-text/-/react-text-9.4.14.tgz", - "integrity": "sha512-QoWtBYene1NhoDc8ZpZaS5t4CrgbXBrN8UsTNXJY2qVgLKctqx3nEP0ZNc9y3/oGOp1bSQ1rIY2SpVv9voMEaA==", + "version": "9.4.23", + "resolved": "https://registry.npmjs.org/@fluentui/react-text/-/react-text-9.4.23.tgz", + "integrity": "sha512-ZQu7yBSULDIbCOvP9B1ViK4BFSb2T6VaWUbv5qfUEV4g24b812EspTYNdoicx7SqdaDt1tSdpy1wFl6QViC5/Q==", + "license": "MIT", "dependencies": { - "@fluentui/react-jsx-runtime": "^9.0.34", - "@fluentui/react-shared-contexts": "^9.15.2", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-shared-contexts": "^9.20.0", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -1543,16 +1744,17 @@ } }, "node_modules/@fluentui/react-textarea": { - "version": "9.3.69", - "resolved": "https://registry.npmjs.org/@fluentui/react-textarea/-/react-textarea-9.3.69.tgz", - "integrity": "sha512-vBxYEDrmjUTlagPsLSqu8PSoCIyuoEn55TtpjVdQdueXeKv8o7Mx7zdkykHP82GJYHfRV7IPIqloEyn+b+ChWA==", + "version": "9.3.83", + "resolved": "https://registry.npmjs.org/@fluentui/react-textarea/-/react-textarea-9.3.83.tgz", + "integrity": "sha512-Oiy5Ahkx44Gpm3g0rch1YyskW7xejiF5gWNR0LgoNJVSFeOS5eHz6GdHOva710IOGVQf71408uFZLancXxFz0A==", + "license": "MIT", "dependencies": { - "@fluentui/react-field": "^9.1.59", - "@fluentui/react-jsx-runtime": "^9.0.34", - "@fluentui/react-shared-contexts": "^9.15.2", + "@fluentui/react-field": "^9.1.71", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-shared-contexts": "^9.20.0", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -1572,22 +1774,23 @@ } }, "node_modules/@fluentui/react-toast": { - "version": "9.3.35", - "resolved": "https://registry.npmjs.org/@fluentui/react-toast/-/react-toast-9.3.35.tgz", - "integrity": "sha512-eBu3ixzcyvRhyLgtWxiYuCWKkeYUZpWqZkRY5m83rJFu+A4yXBpVrCQ/XYdeBe8GuhvxTK7U9AdvMvcY1EBTBg==", + "version": "9.3.51", + "resolved": "https://registry.npmjs.org/@fluentui/react-toast/-/react-toast-9.3.51.tgz", + "integrity": "sha512-msbIdN1x4XlifDDxaoV54aQ+dCjIVH7l9J37mG98ySrjP1F+QpDcaZ87xPb0Cpfg/oUkdvsK6FCeCl6HrWIGWA==", + "license": "MIT", "dependencies": { "@fluentui/keyboard-keys": "^9.0.7", - "@fluentui/react-aria": "^9.10.2", - "@fluentui/react-icons": "^2.0.224", - "@fluentui/react-jsx-runtime": "^9.0.34", - "@fluentui/react-portal": "^9.4.18", - "@fluentui/react-shared-contexts": "^9.15.2", - "@fluentui/react-tabster": "^9.19.5", + "@fluentui/react-aria": "^9.13.2", + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-motion": "^9.4.0", + "@fluentui/react-portal": "^9.4.31", + "@fluentui/react-shared-contexts": "^9.20.0", + "@fluentui/react-tabster": "^9.22.3", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", - "@swc/helpers": "^0.5.1", - "react-transition-group": "^4.4.1" + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", + "@swc/helpers": "^0.5.1" }, "peerDependencies": { "@types/react": ">=16.14.0 <19.0.0", @@ -1597,20 +1800,21 @@ } }, "node_modules/@fluentui/react-toolbar": { - "version": "9.1.76", - "resolved": "https://registry.npmjs.org/@fluentui/react-toolbar/-/react-toolbar-9.1.76.tgz", - "integrity": "sha512-Va0VCe5u1Y+jaf5c0I2YFCImiQLKpG/caEUu10vGmnKqHkeXUIC4o0rbj7IptmiOX7LPbzb/u8BtwFImKHHJJA==", - "dependencies": { - "@fluentui/react-button": "^9.3.73", - "@fluentui/react-context-selector": "^9.1.56", - "@fluentui/react-divider": "^9.2.65", - "@fluentui/react-jsx-runtime": "^9.0.34", - "@fluentui/react-radio": "^9.2.13", - "@fluentui/react-shared-contexts": "^9.15.2", - "@fluentui/react-tabster": "^9.19.5", + "version": "9.1.90", + "resolved": "https://registry.npmjs.org/@fluentui/react-toolbar/-/react-toolbar-9.1.90.tgz", + "integrity": "sha512-nN/18X/ef2uwJ6uDQOq1yv71RGuwOXUVXZHcMdp7OuePqb9G33S9p1s4dp8TFeBO2yzNOB3kh1hNswVVQ4PyFw==", + "license": "MIT", + "dependencies": { + "@fluentui/react-button": "^9.3.87", + "@fluentui/react-context-selector": "^9.1.65", + "@fluentui/react-divider": "^9.2.73", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-radio": "^9.2.27", + "@fluentui/react-shared-contexts": "^9.20.0", + "@fluentui/react-tabster": "^9.22.3", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -1621,19 +1825,20 @@ } }, "node_modules/@fluentui/react-tooltip": { - "version": "9.4.21", - "resolved": "https://registry.npmjs.org/@fluentui/react-tooltip/-/react-tooltip-9.4.21.tgz", - "integrity": "sha512-zGfhuOKDmmfFj9hssKAy00xGYzbxUZDQc4s8tNzP3NPRehuMPSY1ZaPIut3Gvrqn+i8kkKTxXsQBFBz3Qvzq6A==", + "version": "9.4.34", + "resolved": "https://registry.npmjs.org/@fluentui/react-tooltip/-/react-tooltip-9.4.34.tgz", + "integrity": "sha512-mwyuCEiFrVGmyU/W/U2VVGT9i9uKKQMXNoy1NDGjARs7gzxXRvsFr0xME+7PhA8gyvK393rDn9d5aK3XqmuDVA==", + "license": "MIT", "dependencies": { "@fluentui/keyboard-keys": "^9.0.7", - "@fluentui/react-jsx-runtime": "^9.0.34", - "@fluentui/react-portal": "^9.4.18", - "@fluentui/react-positioning": "^9.14.2", - "@fluentui/react-shared-contexts": "^9.15.2", - "@fluentui/react-tabster": "^9.19.5", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-portal": "^9.4.31", + "@fluentui/react-positioning": "^9.15.6", + "@fluentui/react-shared-contexts": "^9.20.0", + "@fluentui/react-tabster": "^9.22.3", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -1644,24 +1849,25 @@ } }, "node_modules/@fluentui/react-tree": { - "version": "9.4.36", - "resolved": "https://registry.npmjs.org/@fluentui/react-tree/-/react-tree-9.4.36.tgz", - "integrity": "sha512-FEv4wG7lOrHDC3l6SFQDzwEz1rnPPeKlL4VR+/kP+R6clKdd2AjE1yeGfrM6j/Ax2iitilMoyqQaZKE/MZBUnQ==", + "version": "9.7.5", + "resolved": "https://registry.npmjs.org/@fluentui/react-tree/-/react-tree-9.7.5.tgz", + "integrity": "sha512-RShNuaSJPKIUPtX7SQ0VXJgWTcSOMjrZQMUQDibqK+YoV1xQbtgs0k3ykU1OeqtwUAdOWjywqwtism4jFlZENg==", + "license": "MIT", "dependencies": { "@fluentui/keyboard-keys": "^9.0.7", - "@fluentui/react-aria": "^9.10.2", - "@fluentui/react-avatar": "^9.6.19", - "@fluentui/react-button": "^9.3.73", - "@fluentui/react-checkbox": "^9.2.18", - "@fluentui/react-context-selector": "^9.1.56", - "@fluentui/react-icons": "^2.0.224", - "@fluentui/react-jsx-runtime": "^9.0.34", - "@fluentui/react-radio": "^9.2.13", - "@fluentui/react-shared-contexts": "^9.15.2", - "@fluentui/react-tabster": "^9.19.5", + "@fluentui/react-aria": "^9.13.2", + "@fluentui/react-avatar": "^9.6.33", + "@fluentui/react-button": "^9.3.87", + "@fluentui/react-checkbox": "^9.2.32", + "@fluentui/react-context-selector": "^9.1.65", + "@fluentui/react-icons": "^2.0.245", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-radio": "^9.2.27", + "@fluentui/react-shared-contexts": "^9.20.0", + "@fluentui/react-tabster": "^9.22.3", "@fluentui/react-theme": "^9.1.19", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -1672,12 +1878,13 @@ } }, "node_modules/@fluentui/react-utilities": { - "version": "9.18.5", - "resolved": "https://registry.npmjs.org/@fluentui/react-utilities/-/react-utilities-9.18.5.tgz", - "integrity": "sha512-Q3WwuHY2YzZSOEg9KlwVKYUzYiWDAiyuuQHE4qZevoiNn2ly2gXgfbVUc27LPdWAOTLT9HjdddsdoaJuJ/S5Mw==", + "version": "9.18.13", + "resolved": "https://registry.npmjs.org/@fluentui/react-utilities/-/react-utilities-9.18.13.tgz", + "integrity": "sha512-Qk9rL5tZI+az77+S2WKwLWu+WOSZZJSIthxp/ImjuiR6CS+LMrVdl0UC8lHpq03QU7hPgNxbbo0cVnCFazU3Lg==", + "license": "MIT", "dependencies": { "@fluentui/keyboard-keys": "^9.0.7", - "@fluentui/react-shared-contexts": "^9.15.2", + "@fluentui/react-shared-contexts": "^9.20.0", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -1686,14 +1893,15 @@ } }, "node_modules/@fluentui/react-virtualizer": { - "version": "9.0.0-alpha.73", - "resolved": "https://registry.npmjs.org/@fluentui/react-virtualizer/-/react-virtualizer-9.0.0-alpha.73.tgz", - "integrity": "sha512-owoCcxzinQFZE4161c4V7rWn8PZR1eeO/2jg3taAUUa7nnyFti6u1wxv2fcRNNtn+hjWcaBtc6bOLVHsEZwoZg==", + "version": "9.0.0-alpha.82", + "resolved": "https://registry.npmjs.org/@fluentui/react-virtualizer/-/react-virtualizer-9.0.0-alpha.82.tgz", + "integrity": "sha512-XvkDXua8Tn9If02RhLPzFMR7CivAZ+frCajQFKaWZjE7OJqYsJ+hKb5ZGz7SWdOPtFq6uxe3H96vG1aDcA0n+w==", + "license": "MIT", "dependencies": { - "@fluentui/react-jsx-runtime": "^9.0.34", - "@fluentui/react-shared-contexts": "^9.15.2", - "@fluentui/react-utilities": "^9.18.5", - "@griffel/react": "^1.5.14", + "@fluentui/react-jsx-runtime": "^9.0.42", + "@fluentui/react-shared-contexts": "^9.20.0", + "@fluentui/react-utilities": "^9.18.13", + "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -1704,11 +1912,12 @@ } }, "node_modules/@fluentui/react-window-provider": { - "version": "2.2.18", - "resolved": "https://registry.npmjs.org/@fluentui/react-window-provider/-/react-window-provider-2.2.18.tgz", - "integrity": "sha512-nBKqxd0P8NmIR0qzFvka1urE2LVbUm6cse1I1T7TcOVNYa5jDf5BrO06+JRZfwbn00IJqOnIVoP0qONqceypWQ==", + "version": "2.2.27", + "resolved": "https://registry.npmjs.org/@fluentui/react-window-provider/-/react-window-provider-2.2.27.tgz", + "integrity": "sha512-Dg0G9bizjryV0Q/r0CPtCVTPa2II/EsT9E6JT3jPSALjQADDLlW4/+ZXbcEC7geZ/40+KpZDmhplvk/AJSFBKg==", + "license": "MIT", "dependencies": { - "@fluentui/set-version": "^8.2.14", + "@fluentui/set-version": "^8.2.23", "tslib": "^2.1.0" }, "peerDependencies": { @@ -1717,34 +1926,37 @@ } }, "node_modules/@fluentui/set-version": { - "version": "8.2.14", - "resolved": "https://registry.npmjs.org/@fluentui/set-version/-/set-version-8.2.14.tgz", - "integrity": "sha512-f/QWJnSeyfAjGAqq57yjMb6a5ejPlwfzdExPmzFBuEOuupi8hHbV8Yno12XJcTW4I0KXEQGw+PUaM1aOf/j7jw==", + "version": "8.2.23", + "resolved": "https://registry.npmjs.org/@fluentui/set-version/-/set-version-8.2.23.tgz", + "integrity": "sha512-VPXaBsiaa3Xn/AY40nLU9bvDQ62lpMVnFzFTlQ8CbpdwrjxNlRxDUY5vRToNzp1+Zu5gD/+CgsXqIZGcry5L5w==", + "license": "MIT", "dependencies": { "tslib": "^2.1.0" } }, "node_modules/@fluentui/style-utilities": { - "version": "8.10.5", - "resolved": "https://registry.npmjs.org/@fluentui/style-utilities/-/style-utilities-8.10.5.tgz", - "integrity": "sha512-pATlzdNhkFghbjPqybq2vrIHnJZ6/bIoVk6tekMVucVedpfLr+TC/2EcRYhRSLyCOjE3qYPhFMZKe850FGDFqA==", - "dependencies": { - "@fluentui/merge-styles": "^8.6.0", - "@fluentui/set-version": "^8.2.14", - "@fluentui/theme": "^2.6.43", - "@fluentui/utilities": "^8.15.0", + "version": "8.10.18", + "resolved": "https://registry.npmjs.org/@fluentui/style-utilities/-/style-utilities-8.10.18.tgz", + "integrity": "sha512-nsXc6LI/UaPrJUh71WIqR19+mmfPl0b4qhaBUOzBGznGKU8jKlHT94pJbAIhWIjytdS8Zk8qtgStI+oYMxz9xg==", + "license": "MIT", + "dependencies": { + "@fluentui/merge-styles": "^8.6.12", + "@fluentui/set-version": "^8.2.23", + "@fluentui/theme": "^2.6.56", + "@fluentui/utilities": "^8.15.13", "@microsoft/load-themed-styles": "^1.10.26", "tslib": "^2.1.0" } }, "node_modules/@fluentui/theme": { - "version": "2.6.43", - "resolved": "https://registry.npmjs.org/@fluentui/theme/-/theme-2.6.43.tgz", - "integrity": "sha512-Z5M0L0xRASWBt13Uj4LiazMKxsWGdno2KeK5Rh+xrSYjAUIXxrJz5Y+VGmpObNsDemyfaYG2TGnTg/b0DDEXtQ==", + "version": "2.6.56", + "resolved": "https://registry.npmjs.org/@fluentui/theme/-/theme-2.6.56.tgz", + "integrity": "sha512-uUDfZpye7e+oXpmP0DOboBYKlyAxbLamnVdWs1a7l6fWEqTNfwDPIPZpMkdDmIBTjE6Q9eHP1u1PmQpMSlz0wA==", + "license": "MIT", "dependencies": { - "@fluentui/merge-styles": "^8.6.0", - "@fluentui/set-version": "^8.2.14", - "@fluentui/utilities": "^8.15.0", + "@fluentui/merge-styles": "^8.6.12", + "@fluentui/set-version": "^8.2.23", + "@fluentui/utilities": "^8.15.13", "tslib": "^2.1.0" }, "peerDependencies": { @@ -1761,13 +1973,15 @@ } }, "node_modules/@fluentui/utilities": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@fluentui/utilities/-/utilities-8.15.0.tgz", - "integrity": "sha512-fj5/LBpt4JPQwx8OZPhHFPHYeCM+a1nnSSpPnVKj2cCZ3o3MoCenw23tgTGdQM5A+i9MKNTE8OuSfMcnTGlA0w==", + "version": "8.15.13", + "resolved": "https://registry.npmjs.org/@fluentui/utilities/-/utilities-8.15.13.tgz", + "integrity": "sha512-DrPv5baKHYtwB+OFqtGiOucdHFbqbnW7TSyxigADYkZQzJj1lnw5DoEGsVyMMVacD4vR21L3JfkMmfrhWm6hyw==", + "license": "MIT", "dependencies": { - "@fluentui/dom-utilities": "^2.2.14", - "@fluentui/merge-styles": "^8.6.0", - "@fluentui/set-version": "^8.2.14", + "@fluentui/dom-utilities": "^2.3.7", + "@fluentui/merge-styles": "^8.6.12", + "@fluentui/react-window-provider": "^2.2.27", + "@fluentui/set-version": "^8.2.23", "tslib": "^2.1.0" }, "peerDependencies": { @@ -1776,24 +1990,26 @@ } }, "node_modules/@griffel/core": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@griffel/core/-/core-1.15.0.tgz", - "integrity": "sha512-+2Li2x6zqQdVBSMbvGSJRxbMbOrXhCEEzX0BK6OMfjdMPJLoR2aaHuAwHL3J9dOpHzFrjp9MMEo4Jzwfo4l6Xw==", + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/@griffel/core/-/core-1.17.1.tgz", + "integrity": "sha512-K3osVOktJ5nioY62idtkjLiIdVcazMwraNxkUMhLtoapDthnKVSC3+gYTuPCBZMdfLH5Hl5Y29YUClRlDjyb7g==", + "license": "MIT", "dependencies": { "@emotion/hash": "^0.9.0", - "@griffel/style-types": "^1.0.2", - "csstype": "^3.1.2", + "@griffel/style-types": "^1.2.0", + "csstype": "^3.1.3", "rtl-css-js": "^1.16.1", "stylis": "^4.2.0", "tslib": "^2.1.0" } }, "node_modules/@griffel/react": { - "version": "1.5.18", - "resolved": "https://registry.npmjs.org/@griffel/react/-/react-1.5.18.tgz", - "integrity": "sha512-Y5L2zvfE+quMPSQPtViMmuDXNCIyJaeeQc5m30VMELgXYN0uk4nbFqwKYXG0FmnHkEHy5MhiGy7q4zCR2+ubTg==", + "version": "1.5.24", + "resolved": "https://registry.npmjs.org/@griffel/react/-/react-1.5.24.tgz", + "integrity": "sha512-WDxWyg182kL/aFCViybARAm/ZFVZoJDiW52pguveZx6cJvvc51esZ2qYJhsbMoy8cqOfnfOrWhOibdb89kgXAQ==", + "license": "MIT", "dependencies": { - "@griffel/core": "^1.15.0", + "@griffel/core": "^1.17.1", "tslib": "^2.1.0" }, "peerDependencies": { @@ -1801,11 +2017,12 @@ } }, "node_modules/@griffel/style-types": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@griffel/style-types/-/style-types-1.0.2.tgz", - "integrity": "sha512-ka/Tpl1WU8js88LObwB/4EvpgXzx/EEJfbHhAr4ZNt29hrQKgL93X1zSY6M/FRhMhWrGIawauWkZP6/y6w/WiQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@griffel/style-types/-/style-types-1.2.0.tgz", + "integrity": "sha512-x166MNw0vWe5l5qhinfNT4eyWOaP48iFzPyFOfIB0/BVidKTWsEe5PmqRJDDtrJFS3VHhd/tE0oM6tkEMh2tsg==", + "license": "MIT", "dependencies": { - "csstype": "^3.1.2" + "csstype": "^3.1.3" } }, "node_modules/@hpcc-js/api": { @@ -1862,13 +2079,12 @@ } }, "node_modules/@hpcc-js/comms": { - "version": "2.93.0", - "resolved": "https://registry.npmjs.org/@hpcc-js/comms/-/comms-2.93.0.tgz", - "integrity": "sha512-FZBiLiraUFGoSnYB6PUv5zMIHcDbdT2TOzRjHl8CF84UK9C9G6G5L19mYwvVOWKTeuSvwaOyOqeOZ6ELcQObrA==", - "license": "Apache-2.0", + "version": "2.94.0", + "resolved": "https://registry.npmjs.org/@hpcc-js/comms/-/comms-2.94.0.tgz", + "integrity": "sha512-+AfJsqj648638hTUeLYd0Thvu1QMHX9zLflrep2xVtz7Wo1OmOiI/mrjClMqK8A8drMa3AduKuQS1R2rL15wZw==", "dependencies": { - "@hpcc-js/ddl-shim": "^2.20.7", - "@hpcc-js/util": "^2.51.1", + "@hpcc-js/ddl-shim": "^2.21.0", + "@hpcc-js/util": "^2.52.0", "@xmldom/xmldom": "0.8.10", "abort-controller": "3.0.0", "node-fetch": "2.7.0", @@ -1877,6 +2093,14 @@ "undici": "5.28.4" } }, + "node_modules/@hpcc-js/comms/node_modules/@hpcc-js/util": { + "version": "2.52.0", + "resolved": "https://registry.npmjs.org/@hpcc-js/util/-/util-2.52.0.tgz", + "integrity": "sha512-WHm/0ApEdWktpPCUG+AFuMnnrDHOTqKXK2oVgyRUAAQJhSWMFxTJxbqIQG7SM9myK58tXzNJrKsP8huzt8X2dg==", + "dependencies": { + "tslib": "2.6.3" + } + }, "node_modules/@hpcc-js/comms/node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -1896,6 +2120,11 @@ } ] }, + "node_modules/@hpcc-js/comms/node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" + }, "node_modules/@hpcc-js/dataflow": { "version": "8.1.7", "resolved": "https://registry.npmjs.org/@hpcc-js/dataflow/-/dataflow-8.1.7.tgz", @@ -1903,9 +2132,9 @@ "license": "Apache-2.0" }, "node_modules/@hpcc-js/ddl-shim": { - "version": "2.20.7", - "resolved": "https://registry.npmjs.org/@hpcc-js/ddl-shim/-/ddl-shim-2.20.7.tgz", - "integrity": "sha512-n+MQBW9zgfhN6zCTaZSiZfMAJfhR6bw4Fuo4fMhQdF2x17Yu/DbN8MReNvyq2OOBmxkwcp28/VxYnsJeppWMQw==", + "version": "2.21.0", + "resolved": "https://registry.npmjs.org/@hpcc-js/ddl-shim/-/ddl-shim-2.21.0.tgz", + "integrity": "sha512-Q66GJqvmTNysmgj/Du8/6+1xIhKgylNu8XVbx1ovNLcLoCxJIhtfWBhCYx0aY2aFHN+QnjC0BGXWyn3xCyxpnw==", "hasInstallScript": true, "dependencies": { "ajv": "6.12.6" @@ -4931,9 +5160,10 @@ } }, "node_modules/csstype": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", - "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" }, "node_modules/current-module-paths": { "version": "1.1.1", @@ -5251,6 +5481,7 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.8.7", "csstype": "^3.0.2" @@ -7486,9 +7717,10 @@ } }, "node_modules/keyborg": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/keyborg/-/keyborg-2.5.0.tgz", - "integrity": "sha512-nb4Ji1suqWqj6VXb61Jrs4ab/UWgtGph4wDch2NIZDfLBUObmLcZE0aiDjZY49ghtu03fvwxDNvS9ZB0XMz6/g==" + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/keyborg/-/keyborg-2.6.0.tgz", + "integrity": "sha512-o5kvLbuTF+o326CMVYpjlaykxqYP9DphFQZ2ZpgrvBouyvOxyEB7oqe8nOLFpiV5VCtz0D3pt8gXQYWpLpBnmA==", + "license": "MIT" }, "node_modules/keygrip": { "version": "1.1.0", @@ -8308,12 +8540,13 @@ } }, "node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, "license": "ISC", "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" } }, "node_modules/minizlib": { @@ -9575,6 +9808,7 @@ "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", "dependencies": { "@babel/runtime": "^7.5.5", "dom-helpers": "^5.0.1", @@ -9931,20 +10165,11 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/rimraf/node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, "node_modules/rtl-css-js": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/rtl-css-js/-/rtl-css-js-1.16.1.tgz", "integrity": "sha512-lRQgou1mu19e+Ya0LsTvKrVJ5TYUbqCVPAiImX3UfLTenarvPUl1QFdvu5Z3PYmHT9RCcwIfbjRQBntExyj3Zg==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.1.2" } @@ -10878,9 +11103,10 @@ } }, "node_modules/stylis": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.0.tgz", - "integrity": "sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ==" + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz", + "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==", + "license": "MIT" }, "node_modules/supports-color": { "version": "7.2.0", @@ -10940,11 +11166,12 @@ } }, "node_modules/tabster": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/tabster/-/tabster-6.1.0.tgz", - "integrity": "sha512-wTPy2d6WVmU/YjT0ERY9jc+et1P/B8FoSQ4qhr1xi7liwTezRbRV6yA1pKx8kdPWmLdIOBA4fn07x9c0x/wnow==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/tabster/-/tabster-8.0.1.tgz", + "integrity": "sha512-Df8La4+IkdbHjupybEDv4rCPSOwx8L3Xh7UVbl0tzyrkiVTKvZg3IRID6KHd/tXbyerO4cXwhY9aOQ+mbEP04w==", + "license": "MIT", "dependencies": { - "keyborg": "2.5.0", + "keyborg": "2.6.0", "tslib": "^2.3.1" } }, @@ -10974,6 +11201,15 @@ "node": ">=10" } }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "license": "ISC", + "engines": { + "node": ">=8" + } + }, "node_modules/terser": { "version": "5.27.0", "resolved": "https://registry.npmjs.org/terser/-/terser-5.27.0.tgz", @@ -11416,6 +11652,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/use-disposable/-/use-disposable-1.0.2.tgz", "integrity": "sha512-UMaXVlV77dWOu4GqAFNjRzHzowYKUKbJBQfCexvahrYeIz4OkUYUjna4Tjjdf92NH8Nm8J7wEfFRgTIwYjO5jg==", + "license": "MIT", "peerDependencies": { "@types/react": ">=16.8.0 <19.0.0", "@types/react-dom": ">=16.8.0 <19.0.0", @@ -11423,6 +11660,15 @@ "react-dom": ">=16.8.0 <19.0.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz", + "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/esp/src/package.json b/esp/src/package.json index bd8daf99417..a30728c2cf2 100644 --- a/esp/src/package.json +++ b/esp/src/package.json @@ -35,16 +35,16 @@ }, "main": "src/stub.js", "dependencies": { - "@fluentui/react": "8.117.0", - "@fluentui/react-components": "9.47.2", - "@fluentui/react-experiments": "8.14.137", - "@fluentui/react-hooks": "8.7.0", - "@fluentui/react-icons-mdl2": "1.3.59", - "@fluentui/react-migration-v8-v9": "9.6.3", + "@fluentui/react": "8.119.3", + "@fluentui/react-components": "9.54.6", + "@fluentui/react-experiments": "8.14.160", + "@fluentui/react-hooks": "8.8.10", + "@fluentui/react-icons-mdl2": "1.3.72", + "@fluentui/react-migration-v8-v9": "9.6.22", "@hpcc-js/chart": "2.83.4", "@hpcc-js/codemirror": "2.62.1", "@hpcc-js/common": "2.71.18", - "@hpcc-js/comms": "2.93.0", + "@hpcc-js/comms": "2.94.0", "@hpcc-js/dataflow": "8.1.7", "@hpcc-js/eclwatch": "2.74.8", "@hpcc-js/graph": "2.85.16", diff --git a/esp/src/src-react/components/EventScheduler.tsx b/esp/src/src-react/components/EventScheduler.tsx index 63b78fcbbcb..2512576b16d 100644 --- a/esp/src/src-react/components/EventScheduler.tsx +++ b/esp/src/src-react/components/EventScheduler.tsx @@ -123,7 +123,7 @@ export const EventScheduler: React.FunctionComponent = ({ const buttons = React.useMemo((): ICommandBarItemProps[] => [ { key: "refresh", text: nlsHPCC.Refresh, iconProps: { iconName: "Refresh" }, - onClick: () => refreshTable.call() + onClick: () => refreshData() }, { key: "divider_1", itemType: ContextualMenuItemType.Divider, onRender: () => }, { @@ -163,7 +163,7 @@ export const EventScheduler: React.FunctionComponent = ({ pushParams(filter); } }, - ], [currentUser, filter, hasFilter, refreshTable, selection, setShowDescheduleConfirm, store, total]); + ], [currentUser.username, filter, hasFilter, refreshData, selection, setShowDescheduleConfirm, store, total]); return } diff --git a/esp/src/src-react/components/IndexFileSummary.tsx b/esp/src/src-react/components/IndexFileSummary.tsx index 54f52605e97..72681e2f6cb 100644 --- a/esp/src/src-react/components/IndexFileSummary.tsx +++ b/esp/src/src-react/components/IndexFileSummary.tsx @@ -64,7 +64,7 @@ export const IndexFileSummary: React.FunctionComponent = React.useEffect(() => { setDescription(file?.Description || ""); - setProtected(file?.ProtectList?.DFUFileProtect?.length > 0 || false); + setProtected(isProtected); setRestricted(file?.IsRestricted || false); if ((file?.filePartsOnCluster() ?? []).length > 0) { @@ -78,7 +78,7 @@ export const IndexFileSummary: React.FunctionComponent = setReplicateFlag(_replicate); } - }, [file]); + }, [file, isProtected]); const canSave = React.useMemo(() => { return file && ( @@ -91,15 +91,11 @@ export const IndexFileSummary: React.FunctionComponent = const buttons = React.useMemo((): ICommandBarItemProps[] => [ { key: "refresh", text: nlsHPCC.Refresh, iconProps: { iconName: "Refresh" }, - onClick: () => { - refresh(); - } + onClick: () => refresh() }, { key: "copyFilename", text: nlsHPCC.CopyLogicalFilename, iconProps: { iconName: "Copy" }, - onClick: () => { - navigator?.clipboard?.writeText(logicalFile); - } + onClick: () => navigator?.clipboard?.writeText(logicalFile) }, { key: "divider_1", itemType: ContextualMenuItemType.Divider, onRender: () => }, { @@ -194,9 +190,15 @@ export const IndexFileSummary: React.FunctionComponent = break; case "isProtected": setProtected(value); + file?.update({ + Protect: value ? WsDfu.DFUChangeProtection.Protect : WsDfu.DFUChangeProtection.Unprotect, + }).catch(err => logger.error(err)); break; case "isRestricted": setRestricted(value); + file?.update({ + Restrict: value ? WsDfu.DFUChangeRestriction.Restrict : WsDfu.DFUChangeRestriction.Unrestricted, + }).catch(err => logger.error(err)); break; } }} /> diff --git a/esp/src/src-react/components/LogicalFileSummary.tsx b/esp/src/src-react/components/LogicalFileSummary.tsx index a2551783357..d412b1c4a28 100644 --- a/esp/src/src-react/components/LogicalFileSummary.tsx +++ b/esp/src/src-react/components/LogicalFileSummary.tsx @@ -69,7 +69,7 @@ export const LogicalFileSummary: React.FunctionComponent { setDescription(file?.Description || ""); - setProtected(file?.ProtectList?.DFUFileProtect?.length > 0 || false); + setProtected(isProtected); setRestricted(file?.IsRestricted || false); if ((file?.filePartsOnCluster() ?? []).length > 0) { @@ -83,7 +83,7 @@ export const LogicalFileSummary: React.FunctionComponent { return file && ( @@ -96,15 +96,11 @@ export const LogicalFileSummary: React.FunctionComponent [ { key: "refresh", text: nlsHPCC.Refresh, iconProps: { iconName: "Refresh" }, - onClick: () => { - refresh(); - } + onClick: () => refresh() }, { key: "copyFilename", text: nlsHPCC.CopyLogicalFilename, iconProps: { iconName: "Copy" }, - onClick: () => { - navigator?.clipboard?.writeText(logicalFile); - } + onClick: () => navigator?.clipboard?.writeText(logicalFile) }, { key: "divider_1", itemType: ContextualMenuItemType.Divider, onRender: () => }, { @@ -216,9 +212,15 @@ export const LogicalFileSummary: React.FunctionComponent logger.error(err)); break; case "isRestricted": setRestricted(value); + file?.update({ + Restrict: value ? WsDfu.DFUChangeRestriction.Restrict : WsDfu.DFUChangeRestriction.Unrestricted, + }).catch(err => logger.error(err)); break; } }} /> diff --git a/esp/src/src-react/components/SuperFileSummary.tsx b/esp/src/src-react/components/SuperFileSummary.tsx index ba5e16172e1..6c063de61e6 100644 --- a/esp/src/src-react/components/SuperFileSummary.tsx +++ b/esp/src/src-react/components/SuperFileSummary.tsx @@ -33,9 +33,9 @@ export const SuperFileSummary: React.FunctionComponent = React.useEffect(() => { setDescription(file?.Description || ""); - setProtected(file?.ProtectList?.DFUFileProtect?.length > 0 || false); + setProtected(isProtected); setRestricted(file?.IsRestricted || false); - }, [file]); + }, [file, isProtected]); const [DeleteConfirm, setShowDeleteConfirm] = useConfirm({ title: nlsHPCC.Delete, @@ -65,9 +65,7 @@ export const SuperFileSummary: React.FunctionComponent = const buttons = React.useMemo((): ICommandBarItemProps[] => [ { key: "refresh", text: nlsHPCC.Refresh, iconProps: { iconName: "Refresh" }, - onClick: () => { - refresh(); - } + onClick: () => refresh() }, { key: "divider_1", itemType: ContextualMenuItemType.Divider, onRender: () => }, { @@ -124,6 +122,9 @@ export const SuperFileSummary: React.FunctionComponent = break; case "isProtected": setProtected(value); + file?.update({ + Protect: value ? WsDfu.DFUChangeProtection.Protect : WsDfu.DFUChangeProtection.Unprotect, + }).catch(err => logger.error(err)); break; } }} /> diff --git a/esp/src/src-react/components/controls/Grid.tsx b/esp/src/src-react/components/controls/Grid.tsx index 634cb318627..f3cdd7a67f1 100644 --- a/esp/src/src-react/components/controls/Grid.tsx +++ b/esp/src/src-react/components/controls/Grid.tsx @@ -1,7 +1,7 @@ import * as React from "react"; -import { DetailsList, DetailsListLayoutMode, Dropdown, IColumn as _IColumn, ICommandBarItemProps, IDetailsHeaderProps, IDetailsListStyles, mergeStyleSets, Selection, Stack, TooltipHost, TooltipOverflowMode, IDetailsList, IRenderFunction, IDetailsRowProps, SelectionMode, ConstrainMode } from "@fluentui/react"; +import { DetailsList, DetailsListLayoutMode, Dropdown, IColumn as _IColumn, ICommandBarItemProps, IDetailsHeaderProps, IDetailsListStyles, mergeStyleSets, Selection, Stack, TooltipHost, TooltipOverflowMode, IRenderFunction, IDetailsRowProps, SelectionMode, ConstrainMode } from "@fluentui/react"; import { Pagination } from "@fluentui/react-experiments/lib/Pagination"; -import { useConst, useId, useMount, useOnEvent } from "@fluentui/react-hooks"; +import { useConst } from "@fluentui/react-hooks"; import { BaseStore, Memory, QueryRequest, QuerySortItem } from "src/store/Memory"; import nlsHPCC from "src/nlsHPCC"; import { createCopyDownloadSelection } from "../Common"; @@ -190,16 +190,6 @@ export function useFluentStoreState({ page }: FluentStoreStateProps): FluentStor return { selection, setSelection, pageNum, setPageNum, pageSize, setPageSize, total, setTotal, refreshTable }; } -interface IListEx { - _scrollElement?: HTMLElement; - _onScroll?: () => void; - _onAsyncScrollDebounced?: () => void; - -} -interface IDetailsListEx extends IDetailsList { - _list?: React.RefObject; -} - interface FluentStoreGridProps { store: any, query?: QueryRequest, @@ -308,30 +298,8 @@ const FluentStoreGrid: React.FunctionComponent = ({ columnWidths.set(column.key, newWidth); }, [columnWidths]); - /* Monitor Scroll Events (hack) - - Essentially we are setting the scrollElement of the DetailsList to the div that contains the DetailsList (rather than a scrollable pane host). - See: https://github.com/microsoft/fluentui/blob/55d3a31042e8972ea373841bef616c68e6ab69f9/packages/react/src/components/List/List.tsx#L355-L369 - - Note: Not sure if `_onScroll` call is needed, but excluding for now as it seems to work without it and is more performant. - */ - const id = useId("fluent-store-grid-"); - const detailListComponent = React.useRef(null); - const [detailListElement, setDetailListElement] = React.useState(null); - useMount(() => { - const detailListElement = document.querySelector(`#${id} .ms-DetailsList`); - setDetailListElement(detailListElement); - if (detailListComponent.current?._list?.current) { - detailListComponent.current._list.current._scrollElement = detailListElement; - } - }); - useOnEvent(detailListElement, "scroll", () => { - detailListComponent.current?._list?.current?._onAsyncScrollDebounced(); - }); - - return
+ return
= ({ const keys = selectedKey !== "" ? selectedKey.split(valueSeparator) : []; let items = [...selectedItems]; if (keys.length === items.length) return; - if (selectedKeys !== "" && selOptions.length && selectedKey === "") { + if (selectedKeys !== "" && selOptions.length && selectedKey === "" && selectedKeys === items.map(i => i.key).join("|")) { setSelectedItems([]); return; } @@ -833,7 +833,7 @@ export const CloudContainerNameField: React.FunctionComponent; @@ -1367,6 +1367,7 @@ export function createInputs(fields: Fields, onChange?: (id: string, newValue: a label: field.label, field: { onChange(fieldID, row.key); setDropzone(row.key as string); diff --git a/esp/src/src-react/hooks/file.ts b/esp/src/src-react/hooks/file.ts index 7ea78a2cc70..fc83c9853d0 100644 --- a/esp/src/src-react/hooks/file.ts +++ b/esp/src/src-react/hooks/file.ts @@ -19,13 +19,13 @@ export function useFile(cluster: string, name: string): [LogicalFile, boolean, n let handle; const fetchInfo = singletonDebounce(file, "fetchInfo"); fetchInfo() - .then(() => { + .then((response) => { if (active) { setFile(file); - setIsProtected(file.ProtectList?.DFUFileProtect?.length > 0 || false); + setIsProtected(response.ProtectList?.DFUFileProtect?.length > 0 || false); setLastUpdate(Date.now()); handle = file.watch(() => { - setIsProtected(file.ProtectList?.DFUFileProtect?.length > 0 || false); + setIsProtected(response.ProtectList?.DFUFileProtect?.length > 0 || false); setLastUpdate(Date.now()); }); } diff --git a/esp/test/httptest/httptest.cpp b/esp/test/httptest/httptest.cpp index 3f5515207fa..b28e3b1edbf 100644 --- a/esp/test/httptest/httptest.cpp +++ b/esp/test/httptest/httptest.cpp @@ -565,7 +565,7 @@ int HttpClient::sendRequest(int times, HttpStat& stat, StringBuffer& req) unsigned int sizeread; do { - socket->read(tmpbuf, 0, 256, sizeread); + socket->read(tmpbuf, 1, 256, sizeread); } while(sizeread > 0); diff --git a/esp/tools/soapplus/http.cpp b/esp/tools/soapplus/http.cpp index 69344b14d08..009a8b39870 100644 --- a/esp/tools/soapplus/http.cpp +++ b/esp/tools/soapplus/http.cpp @@ -1087,14 +1087,13 @@ class CSimpleSocket : public CInterface, implements IInterface ISecureSocketContext* m_ssctx; bool m_isSSL; FILE* m_logfile; - int m_sockfd; - Owned m_securesocket; + Owned m_socket; bool m_connected; public: IMPLEMENT_IINTERFACE; - CSimpleSocket(ISecureSocketContext* ssctx, FILE* logfile) : m_connected (false) + CSimpleSocket(ISecureSocketContext* ssctx, FILE* logfile) : m_connected(false) { m_ssctx = ssctx; if(ssctx) @@ -1102,13 +1101,6 @@ class CSimpleSocket : public CInterface, implements IInterface else m_isSSL = false; m_logfile = logfile; - m_sockfd = -1; - } - - virtual ~CSimpleSocket() - { - if(m_sockfd > 0) - ::closesocket(m_sockfd); } int connect(CAddress* address) @@ -1116,154 +1108,76 @@ class CSimpleSocket : public CInterface, implements IInterface if(!address) return -1; - m_sockfd = socket(AF_INET, SOCK_STREAM, 0); - if (m_sockfd == INVALID_SOCKET) + SocketEndpoint ep(address->m_fqdn, address->m_port); + try { - fprintf(m_logfile, "Error: invalid socket"); - return -1; + m_socket.setown(ISocket::connect(ep)); } - int ret = ::connect(m_sockfd, (struct sockaddr *)(address->m_addr), sizeof(*(address->m_addr))); - if(ret < 0) + catch (IJSOCK_Exception *e) { - char errbuf[512]; - GetLastErrorAndMessage(errbuf); - fprintf(m_logfile, "Error: failed to connect to %s:%d - %s", address->m_ip.str(), address->m_port, errbuf); + EXCLOG(e); + e->Release(); return -1; } if(m_ssctx != NULL) { - m_securesocket.setown(m_ssctx->createSecureSocket(m_sockfd, SSLogNormal, address->m_fqdn.str())); + Owned m_securesocket = m_ssctx->createSecureSocket(m_socket.getClear(), SSLogNormal, address->m_fqdn.str()); int res = m_securesocket->secure_connect(); if(res < 0) { fprintf(m_logfile, "Error: failed to establish ssl connection\n"); return -1; } + m_socket.setown(m_securesocket.getClear()); } m_connected = true; return 0; } - ssize_t readn(int fd, void *vptr, size_t min, size_t n) + ssize_t writen(const void *vptr, size_t n) { - if (min > n) - return 0; - - size_t nleft; - ssize_t nread; - char *ptr; - - ptr = (char *)vptr; - nleft = n; - while (nleft > 0) + try { - if ((nread = ::recv(fd, ptr, nleft, 0)) < 0) - { - if (errno == EINTR) - { - nread = 0; /* and call read() again */ - continue; - } - else - { - close(); - return (-1); - } - } - else if (nread == 0) - { - close(); - break; /* EOF */ - } - - nleft -= nread; - ptr += nread; - if (n - nleft >= min) - break; + return m_socket->write(vptr, n); } - return (n - nleft); /* return >= 0 */ - } - - ssize_t readn(int fd, void *vptr, size_t n) - { - return readn(fd, vptr, n, n); - } - - ssize_t writen(int fd, const void *vptr, size_t n) - { - size_t nleft; - ssize_t nwritten; - const char *ptr; - - ptr = (char *)vptr; - nleft = n; - while (nleft > 0) + catch (IJSOCK_Exception *e) { - if ( (nwritten = ::send(fd, ptr, nleft, 0)) <= 0) - { - if (nwritten < 0 && errno == EINTR) - nwritten = 0; /* and call write() again */ - else - { - close(); - return (-1); /* error */ - } - } - - nleft -= nwritten; - ptr += nwritten; + EXCLOG(e); + e->Release(); + return -1; } - return (n); } int send(StringBuffer& data) { - int sent = 0; - if(!m_isSSL) - { - // sent = ::send(m_sockfd, data.str(), data.length(), 0); - sent = writen(m_sockfd, data.str(), data.length()); - } - else - sent = m_securesocket->write(data.str(), data.length()); - - return sent; - } - - int receive(char* buf, int buflen) - { - return receive(buf, buflen, buflen); + return writen(data.str(), data.length()); } int receive(char* buf, int min, int buflen) { - if (min > buflen) + if ((min > buflen) || !m_socket) return 0; unsigned int len = 0; - if (!m_isSSL) - { - //len = ::recv(m_sockfd, buf, buflen, 0); - len = readn(m_sockfd, buf, min, buflen); - } - else - { - m_securesocket->read(buf, min, buflen, len, WAIT_FOREVER); - if(len <= 0) - close(); - } + if (readtmsAllowClose(m_socket, buf, min, buflen, len, WAIT_FOREVER) || (len <= 0)) + close(); return len; } + int receive(char* buf, int buflen) + { + return receive(buf, buflen, buflen); + } + void close() { - if(m_sockfd > 0) + if (m_socket) { - ::closesocket(m_sockfd); - m_sockfd = -1; + m_socket->close(); + m_socket.clear(); } m_connected = false; } @@ -1316,101 +1230,72 @@ int HttpClient::sendStressRequest(StringBuffer& request, HttpStat* stat, Ownedreceive(recvbuf, 1, 2047); + if (len > 0) { - len = sock->receive(recvbuf, 0, 2047); - if (len > 0) - { - total_len += len; - recvbuf[len] = 0; - if (m_doValidation) - xml.append(len, recvbuf); - if (http_tracelevel >= 10) - fprintf(m_logfile, "%s", recvbuf); - } - else + total_len += len; + recvbuf[len] = 0; + if (http_tracelevel >= 10) + fprintf(m_logfile, "%s", recvbuf); + headersbuf.append(recvbuf); + char* endofheaders = strstr((char*)(headersbuf.str()+searchStart), "\r\n\r\n"); + searchStart += len>3?(len-3):0; + if (endofheaders != nullptr) { - if (total_len == 0) - { - if (stat) - stat->numfails++; - return -1; - } - break; - } - } - } - else - { - StringBuffer headersbuf; - unsigned int searchStart = 0; - while (1) - { - len = sock->receive(recvbuf, 0, 2047); - if (len > 0) - { - total_len += len; - recvbuf[len] = 0; - if (http_tracelevel >= 10) - fprintf(m_logfile, "%s", recvbuf); - headersbuf.append(recvbuf); - char* endofheaders = strstr((char*)(headersbuf.str()+searchStart), "\r\n\r\n"); - searchStart += len>3?(len-3):0; - if (endofheaders != nullptr) + int conlen = 0; + const char* conlenstr = strstr((char*)headersbuf.str(), "Content-Length:"); + if (conlenstr != nullptr) + conlen = atoi(conlenstr+15); + else + shouldClose = true; + if (conlen > 0) { - int conlen = 0; - const char* conlenstr = strstr((char*)headersbuf.str(), "Content-Length:"); - if (conlenstr != nullptr) - conlen = atoi(conlenstr+15); - else - shouldClose = true; - if (conlen > 0) + int content_read = headersbuf.length() - (endofheaders+4 - headersbuf.str()); + if (m_doValidation && content_read > 0) + xml.append(content_read, endofheaders+4); + while (content_read < conlen) { - int content_read = headersbuf.length() - (endofheaders+4 - headersbuf.str()); - if (m_doValidation && content_read > 0) - xml.append(content_read, endofheaders+4); - while (content_read < conlen) + int remaining = conlen - content_read; + len = sock->receive(recvbuf, 2047>remaining?remaining:2047); + if (len > 0) { - int remaining = conlen - content_read; - len = sock->receive(recvbuf, 2047>remaining?remaining:2047); - if (len > 0) - { - content_read += len; - total_len += len; - recvbuf[len] = 0; - if (m_doValidation) - xml.append(len, recvbuf); - if (http_tracelevel >= 10) - fprintf(m_logfile, "%s", recvbuf); - } - else - { - sock->close(); - if (stat) - stat->numfails++; - return -1; - } + content_read += len; + total_len += len; + recvbuf[len] = 0; + if (m_doValidation) + xml.append(len, recvbuf); + if (http_tracelevel >= 10) + fprintf(m_logfile, "%s", recvbuf); + } + else + { + sock->close(); + if (stat) + stat->numfails++; + return -1; } } - break; - } - else if (total_len >= 1000000) // Still haven't reached end of headers - { - fprintf(m_logfile, "HTTP headers too long.\n"); - shouldClose = true; - break; } + break; } - else + else if (total_len >= 1000000) // Still haven't reached end of headers { - sock->close(); - if (stat && len < 0) - stat->numfails++; - return -1; + fprintf(m_logfile, "HTTP headers too long.\n"); + shouldClose = true; + break; } } + else + { + sock->close(); + if (stat && len < 0) + stat->numfails++; + return -1; + } } if (m_doValidation && xml.length() > 0) validate(xml); diff --git a/esp/tools/soapplus/httpproxy.cpp b/esp/tools/soapplus/httpproxy.cpp index 59e0fd0fcdd..345f4b92552 100644 --- a/esp/tools/soapplus/httpproxy.cpp +++ b/esp/tools/soapplus/httpproxy.cpp @@ -676,7 +676,7 @@ class CSocksProxyThread : public Thread ip.getHostText(ipstr); char inbuf2[16]; - m_client->read(inbuf2, 0, 16, lenread); + readtmsAllowClose(m_client, inbuf2, 1, 16, lenread, WAIT_FOREVER); StringBuffer username; while(lenread > 0) { @@ -694,7 +694,7 @@ class CSocksProxyThread : public Thread if (done) break; username.append(lenread, inbuf2); - m_client->read(inbuf2, 0, 16, lenread); + readtmsAllowClose(m_client, inbuf2, 1, 16, lenread, WAIT_FOREVER); } if(http_tracelevel >= 5) diff --git a/fs/dafsserver/dafsserver.cpp b/fs/dafsserver/dafsserver.cpp index 282559612fe..e16581bc4b7 100644 --- a/fs/dafsserver/dafsserver.cpp +++ b/fs/dafsserver/dafsserver.cpp @@ -2934,68 +2934,87 @@ class CRemoteFileServer : implements IRemoteFileServer, public CInterface touch(); try { - if (!gotSize) + while (true) { - // left represents amount we have read of leading size32_t (normally expect to be read in 1 go) - if (0 == msg.length()) // 1st time - msgWritePtr = (byte *)msg.reserveTruncate(sizeof(size32_t)); - size32_t szRead; - sock->read(msgWritePtr, 1, sizeof(size32_t)-left, szRead); - left += szRead; - msgWritePtr += szRead; - if (left == sizeof(size32_t)) + if (!gotSize) { - gotSize = true; - msg.read(left); - msg.clear(); - try + // left represents amount we have read of leading size32_t (normally expect to be read in 1 go) + if (0 == msg.length()) // 1st time + msgWritePtr = (byte *)msg.reserveTruncate(sizeof(size32_t)); + size32_t szRead; + sock->read(msgWritePtr, 0, sizeof(size32_t)-left, szRead, WAIT_FOREVER, false); + + left += szRead; + msgWritePtr += szRead; + if (left == sizeof(size32_t)) // if not, we exit, and rely on next notifySelected { - msgWritePtr = (byte *)msg.reserveTruncate(left); - } - catch (IException *e) - { - EXCLOG(e,"notifySelected(1)"); - e->Release(); - left = 0; - // if too big then suggest corrupted packet, try to consume - // JCSMORE this seems a bit pointless, and it used to only read last 'avail', - // which is not necessarily everything that was sent - char fbuf[1024]; - while (true) + gotSize = true; + msg.read(left); + msg.clear(); + try { - try - { - size32_t szRead; - sock->read(fbuf, 1, 1024, szRead); - } - catch (IException *e) + msgWritePtr = (byte *)msg.reserveTruncate(left); + } + catch (IException *e) + { + EXCLOG(e,"notifySelected(1)"); + e->Release(); + left = 0; + // if too big then suggest corrupted packet, try to consume + // JCSMORE this seems a bit pointless, and it used to only read last 'avail', + // which is not necessarily everything that was sent + char fbuf[1024]; + while (true) { - EXCLOG(e,"notifySelected(2)"); - e->Release(); - break; + try + { + size32_t szRead; + sock->read(fbuf, 0, 1024, szRead, WAIT_FOREVER, true); + } + catch (IException *e) + { + EXCLOG(e,"notifySelected(2)"); + e->Release(); + break; + } } } + if (0 == left) + { + gotSize = false; + msg.clear(); + parent->onCloseSocket(this, 5); + return true; + } } - if (0 == left) - { - gotSize = false; - msg.clear(); - parent->onCloseSocket(this, 5); - return true; - } + else + break; // wait for rest via subsequent notifySelected's } - } - else // left represents length of message remaining to receive - { - size32_t szRead; - sock->read(msgWritePtr, 1, left, szRead); - msgWritePtr += szRead; - left -= szRead; - if (0 == left) // NB: only ever here if original size was >0 + bool gc = false; + if (gotSize) // left represents length of message remaining to receive { - gotSize = false; // reset for next packet - parent->handleCompleteMessage(this, msg); // consumes msg + size32_t szRead; + gc = readtmsAllowClose(sock, msgWritePtr, 0, left, szRead, WAIT_FOREVER); + msgWritePtr += szRead; + left -= szRead; + if (0 == left) // NB: only ever here if original size was >0 + { + gotSize = false; // reset for next packet + parent->handleCompleteMessage(this, msg); // consumes msg + if (gc) + THROWJSOCKEXCEPTION(JSOCKERR_graceful_close); + } + else + { + if (gc) + THROWJSOCKEXCEPTION(JSOCKERR_graceful_close); + break; // wait for rest via subsequent notifySelected's + } } + else if (gc) + THROWJSOCKEXCEPTION(JSOCKERR_graceful_close); + // to be here, implies handled full message, loop around to see if more on the wire. + // will break out if nothing/partial. } } catch (IJSOCK_Exception *e) @@ -5620,7 +5639,7 @@ class CRemoteFileServer : implements IRemoteFileServer, public CInterface addClient(sockSSL.getClear(), true, false); } - if (rowServiceSockAvail) + if (!isContainerized() && rowServiceSockAvail) // in contaierized each service is on a single dedicated port, the below 2 cases are for BM only { #ifdef _DEBUG acceptedRSSock->getPeerEndpoint(eps); @@ -5659,10 +5678,7 @@ class CRemoteFileServer : implements IRemoteFileServer, public CInterface clients.append(*client.getLink()); } // JCSMORE - perhaps cap # added here... ? - unsigned mode = SELECTMODE_READ; - if (secure) - mode |= SELECTMODE_WRITE; - selecthandler->add(sock, mode, client); + selecthandler->add(sock, SELECTMODE_READ, client); } void stop() diff --git a/helm/hpcc/templates/issuers.yaml b/helm/hpcc/templates/issuers.yaml index 280c29198a7..9b52fb85800 100644 --- a/helm/hpcc/templates/issuers.yaml +++ b/helm/hpcc/templates/issuers.yaml @@ -136,7 +136,7 @@ spec: {{- range $k, $v := .Values.certificates.issuers }} {{- include "hpcc.addIssuer" (dict "root" $ "issuerKeyName" $k "me" $v ) }} {{- end }} - {{- $categories := list "system" "storage" "esp" "codeSign" "codeVerify" "authn" "eclUser" "ecl" "git" "jfrog" -}} + {{- $categories := list "authn" "codeSign" "codeVerify" "ecl" "eclUser" "esp" "git" "jfrog" "storage" "system" -}} {{- range $category := $categories }} {{ include "hpcc.addVaultClientCertificate" (dict "root" $ "category" $category) }} {{- end }} diff --git a/helm/hpcc/values.schema.json b/helm/hpcc/values.schema.json index f9be3740b9d..b2e1e742d85 100644 --- a/helm/hpcc/values.schema.json +++ b/helm/hpcc/values.schema.json @@ -69,10 +69,13 @@ "timeout": { "type": "integer" }, - "storage": { + "authn": { "$ref": "#/definitions/secrets" }, - "authn": { + "codeSign": { + "$ref": "#/definitions/secrets" + }, + "codeVerify": { "$ref": "#/definitions/secrets" }, "ecl": { @@ -81,20 +84,19 @@ "eclUser": { "$ref": "#/definitions/secrets" }, - "codeSign": { + "esp": { "$ref": "#/definitions/secrets" }, - "codeVerify": { + "git": { "$ref": "#/definitions/secrets" }, - "git": { + "jfrog": { "$ref": "#/definitions/secrets" }, - "system": { + "storage": { "$ref": "#/definitions/secrets" - } - , - "esp": { + }, + "system": { "$ref": "#/definitions/secrets" } }, @@ -107,13 +109,13 @@ "timeout": { "type": "integer" }, - "storage": { + "authn": { "$ref": "#/definitions/vaultCategory" }, - "authn": { + "codeSign": { "$ref": "#/definitions/vaultCategory" }, - "esp": { + "codeVerify": { "$ref": "#/definitions/vaultCategory" }, "ecl": { @@ -122,16 +124,20 @@ "eclUser": { "$ref": "#/definitions/vaultCategory" }, - "codeSign": { + "esp": { "$ref": "#/definitions/vaultCategory" }, - "codeVerify": { + "git": { "$ref": "#/definitions/vaultCategory" }, - "git": { + "jfrog": { + "$ref": "#/definitions/vaultCategory" + }, + "storage": { "$ref": "#/definitions/vaultCategory" } }, + "$comment": "The system secrets are used for vault configuration so cannot themselves be defined in a vault", "additionalProperties": false }, "bundles": { diff --git a/roxie/udplib/udpsha.cpp b/roxie/udplib/udpsha.cpp index 36fe5b4aa80..65d66b4c296 100644 --- a/roxie/udplib/udpsha.cpp +++ b/roxie/udplib/udpsha.cpp @@ -986,14 +986,14 @@ void CSimulatedQueueReadSocket::writeOwnSimulatedPacket(void const* buf, size32_ avail.signal(); } -void CSimulatedQueueReadSocket::read(void* buf, size32_t min_size, size32_t max_size, size32_t &size_read, unsigned timeoutsecs) +void CSimulatedQueueReadSocket::read(void* buf, size32_t min_size, size32_t max_size, size32_t &size_read, unsigned timeoutsecs, bool suppresGCIfMinSize) { unsigned tms = timeoutsecs == WAIT_FOREVER ? WAIT_FOREVER : timeoutsecs * 1000; readtms(buf, min_size, max_size, size_read, tms); } void CSimulatedQueueReadSocket::readtms(void* buf, size32_t min_size, size32_t max_size, size32_t &size_read, - unsigned timeout) + unsigned timeout, bool suppresGCIfMinSize) { size_read = 0; if (!timeout || wait_read(timeout)) @@ -1054,11 +1054,11 @@ CSimulatedUdpReadSocket::~CSimulatedUdpReadSocket() size32_t CSimulatedUdpReadSocket::get_receive_buffer_size() { return realSocket->get_receive_buffer_size(); } void CSimulatedUdpReadSocket::set_receive_buffer_size(size32_t sz) { realSocket->set_receive_buffer_size(sz); } -void CSimulatedUdpReadSocket::read(void* buf, size32_t min_size, size32_t max_size, size32_t &size_read, unsigned timeoutsecs) +void CSimulatedUdpReadSocket::read(void* buf, size32_t min_size, size32_t max_size, size32_t &size_read, unsigned timeoutsecs, bool suppresGCIfMinSize) { realSocket->read(buf, min_size, max_size, size_read, timeoutsecs); } -void CSimulatedUdpReadSocket::readtms(void* buf, size32_t min_size, size32_t max_size, size32_t &size_read, unsigned timeout) +void CSimulatedUdpReadSocket::readtms(void* buf, size32_t min_size, size32_t max_size, size32_t &size_read, unsigned timeout, bool suppresGCIfMinSize) { realSocket->readtms(buf, min_size, max_size, size_read, timeout); } diff --git a/roxie/udplib/udpsha.hpp b/roxie/udplib/udpsha.hpp index a111f75143d..116f7e66a02 100644 --- a/roxie/udplib/udpsha.hpp +++ b/roxie/udplib/udpsha.hpp @@ -339,9 +339,9 @@ class CSocketSimulator : public CInterfaceOf { private: virtual void read(void* buf, size32_t min_size, size32_t max_size, size32_t &size_read, - unsigned timeoutsecs = WAIT_FOREVER) override { UNIMPLEMENTED; } + unsigned timeoutsecs = WAIT_FOREVER, bool suppresGCIfMinSize = true) override { UNIMPLEMENTED; } virtual void readtms(void* buf, size32_t min_size, size32_t max_size, size32_t &size_read, - unsigned timeout) override { UNIMPLEMENTED; } + unsigned timeout, bool suppresGCIfMinSize = true) override { UNIMPLEMENTED; } virtual void read(void* buf, size32_t size) override { UNIMPLEMENTED; } virtual size32_t write(void const* buf, size32_t size) override { UNIMPLEMENTED; } virtual size32_t writetms(void const* buf, size32_t minSize, size32_t size, unsigned timeoutms=WAIT_FOREVER) override { UNIMPLEMENTED; } @@ -442,9 +442,9 @@ class CSimulatedQueueReadSocket : public CSocketSimulator virtual size32_t get_receive_buffer_size() override { return max; } virtual void set_receive_buffer_size(size32_t sz) override { max = sz; } virtual void read(void* buf, size32_t min_size, size32_t max_size, size32_t &size_read, - unsigned timeoutsecs = WAIT_FOREVER) override; + unsigned timeoutsecs = WAIT_FOREVER, bool suppresGCIfMinSize = true) override; virtual void readtms(void* buf, size32_t min_size, size32_t max_size, size32_t &size_read, - unsigned timeout) override; + unsigned timeout, bool suppresGCIfMinSize = true) override; virtual int wait_read(unsigned timeout) override; virtual void close() override {} virtual void shutdown(unsigned mode) override { } @@ -491,8 +491,8 @@ class CSimulatedUdpReadSocket : public CSimulatedUdpSocket virtual size32_t get_receive_buffer_size() override; virtual void set_receive_buffer_size(size32_t sz) override; - virtual void read(void* buf, size32_t min_size, size32_t max_size, size32_t &size_read, unsigned timeoutsecs = WAIT_FOREVER) override; - virtual void readtms(void* buf, size32_t min_size, size32_t max_size, size32_t &size_read, unsigned timeout) override; + virtual void read(void* buf, size32_t min_size, size32_t max_size, size32_t &size_read, unsigned timeoutsecs = WAIT_FOREVER, bool suppresGCIfMinSize = true) override; + virtual void readtms(void* buf, size32_t min_size, size32_t max_size, size32_t &size_read, unsigned timeout, bool suppresGCIfMinSize = true) override; virtual int wait_read(unsigned timeout) override; virtual void close() override; diff --git a/roxie/udplib/udptrr.cpp b/roxie/udplib/udptrr.cpp index 9a31fc0d287..984b5a164cc 100644 --- a/roxie/udplib/udptrr.cpp +++ b/roxie/udplib/udptrr.cpp @@ -22,6 +22,7 @@ #include "jthread.hpp" #include "jlog.hpp" +#include "jmisc.hpp" #include "jisem.hpp" #include "jsocket.hpp" #include "udplib.hpp" @@ -853,6 +854,31 @@ class CReceiveManager : implements IReceiveManager, public CInterface IpMapOf sendersTable; + class UdpRdTracker : public TimeDivisionTracker<6, false> + { + public: + enum + { + other, + waiting, + allocating, + processing, + pushing, + checkingPending + }; + + UdpRdTracker(const char *name, unsigned reportIntervalSeconds) : TimeDivisionTracker<6, false>(name, reportIntervalSeconds) + { + stateNames[other] = "other"; + stateNames[waiting] = "waiting"; + stateNames[allocating] = "allocating"; + stateNames[processing] = "processing"; + stateNames[pushing] = "pushing"; + stateNames[checkingPending] = "checking pending"; + } + + }; + class receive_receive_flow : public Thread { CReceiveManager &parent; @@ -863,7 +889,7 @@ class CReceiveManager : implements IReceiveManager, public CInterface std::atomic running = { false }; SenderList pendingRequests; // List of senders requesting permission to send PermitList pendingPermits; // List of active permits - + UdpRdTracker timeTracker; private: void noteRequest(UdpSenderEntry *requester, sequence_t flowSeq, sequence_t sendSeq) { @@ -966,7 +992,8 @@ class CReceiveManager : implements IReceiveManager, public CInterface public: receive_receive_flow(CReceiveManager &_parent, unsigned flow_p, unsigned _maxSlotsPerSender) - : Thread("UdpLib::receive_receive_flow"), parent(_parent), flow_port(flow_p), maxSlotsPerSender(_maxSlotsPerSender), maxPermits(_parent.input_queue_size) + : Thread("UdpLib::receive_receive_flow"), parent(_parent), flow_port(flow_p), maxSlotsPerSender(_maxSlotsPerSender), maxPermits(_parent.input_queue_size), + timeTracker("receive_receive_flow", 60) { } @@ -1210,6 +1237,7 @@ class CReceiveManager : implements IReceiveManager, public CInterface { DBGLOG("UdpReceiver: wait_read(%u)", timeout); } + UdpRdTracker::TimeDivision d(timeTracker, UdpRdTracker::waiting); bool dataAvail = flow_socket->wait_read(timeout); if (dataAvail) { @@ -1217,8 +1245,10 @@ class CReceiveManager : implements IReceiveManager, public CInterface unsigned int res ; flow_socket->readtms(&msg, l, l, res, 0); assert(res==l); + d.switchState(UdpRdTracker::processing); doFlowRequest(msg); } + d.switchState(UdpRdTracker::checkingPending); timeout = checkPendingRequests(); } catch (IException *e) @@ -1263,9 +1293,10 @@ class CReceiveManager : implements IReceiveManager, public CInterface ISocket *selfFlowSocket = nullptr; std::atomic running = { false }; Semaphore started; + UdpRdTracker timeTracker; public: - receive_data(CReceiveManager &_parent) : Thread("UdpLib::receive_data"), parent(_parent) + receive_data(CReceiveManager &_parent) : Thread("UdpLib::receive_data"), parent(_parent), timeTracker("receive_data", 60) { unsigned ip_buffer = parent.input_queue_size*DATA_PAYLOAD*2; if (ip_buffer < udpFlowSocketsSize) ip_buffer = udpFlowSocketsSize; @@ -1325,7 +1356,7 @@ class CReceiveManager : implements IReceiveManager, public CInterface adjustPriority(2); #endif started.signal(); - unsigned lastOOOReport = 0; + unsigned lastOOOReport = msTick(); unsigned lastPacketsOOO = 0; unsigned lastUnwantedDiscarded = 0; unsigned timeout = 5000; @@ -1342,8 +1373,10 @@ class CReceiveManager : implements IReceiveManager, public CInterface //Read at least the size of the smallest packet we can receive //static assert to check we are reading the smaller of the two possible packet types static_assert(sizeof(UdpRequestToSendMsg) <= sizeof(UdpPacketHeader)); - receive_socket->readtms(b->data, sizeof(UdpRequestToSendMsg), DATA_PAYLOAD, res, timeout); - + { + UdpRdTracker::TimeDivision d(timeTracker, UdpRdTracker::waiting); + receive_socket->readtms(b->data, sizeof(UdpRequestToSendMsg), DATA_PAYLOAD, res, timeout); + } //Even if a UDP packet is not split, very occasionally only some of the data may be present for the read. //Slightly horribly this packet could be one of two different formats(!) // a UdpRequestToSendMsg, which has a 2 byte command at the start of the header, with a maximum value of max_flow_cmd @@ -1372,27 +1405,30 @@ class CReceiveManager : implements IReceiveManager, public CInterface //Redirect them to the flow thread to process them. selfFlowSocket->write(b->data, res); } - - dataPacketsReceived++; - UdpSenderEntry *sender = &parent.sendersTable[hdr.node]; - if (sender->noteSeen(hdr)) - { - // We should perhaps track how often this happens, but it's not the same as unwantedDiscarded - hdr.node.clear(); // Used to indicate a duplicate that collate thread should discard. We don't discard on this thread as don't want to do anything that requires locks... - } - else { - //Decrease the number of active reservations to balance having received a new data packet (otherwise they will be double counted) - sender->decPermit(hdr.msgSeq); - if (udpTraceLevel > 5) // don't want to interrupt this thread if we can help it + UdpRdTracker::TimeDivision d(timeTracker, UdpRdTracker::processing); + dataPacketsReceived++; + UdpSenderEntry *sender = &parent.sendersTable[hdr.node]; + if (sender->noteSeen(hdr)) { - StringBuffer s; - DBGLOG("UdpReceiver: %u bytes received packet %" SEQF "u %x from %s", res, hdr.sendSeq, hdr.pktSeq, hdr.node.getTraceText(s).str()); + // We should perhaps track how often this happens, but it's not the same as unwantedDiscarded + hdr.node.clear(); // Used to indicate a duplicate that collate thread should discard. We don't discard on this thread as don't want to do anything that requires locks... + } + else + { + //Decrease the number of active reservations to balance having received a new data packet (otherwise they will be double counted) + sender->decPermit(hdr.msgSeq); + if (udpTraceLevel > 5) // don't want to interrupt this thread if we can help it + { + StringBuffer s; + DBGLOG("UdpReceiver: %u bytes received packet %" SEQF "u %x from %s", res, hdr.sendSeq, hdr.pktSeq, hdr.node.getTraceText(s).str()); + } } + d.switchState(UdpRdTracker::pushing); + parent.input_queue->pushOwn(b); + d.switchState(UdpRdTracker::allocating); + b = udpBufferManager->allocate(); } - parent.input_queue->pushOwn(b); - b = udpBufferManager->allocate(); - if (udpStatsReportInterval) { unsigned now = msTick(); @@ -1537,11 +1573,14 @@ class CReceiveManager : implements IReceiveManager, public CInterface void collatePackets() { + UdpRdTracker timeTracker("collatePackets", 60); while(running) { try { + UdpRdTracker::TimeDivision d(timeTracker, UdpRdTracker::waiting); DataBuffer *dataBuff = input_queue->pop(true); + d.switchState(UdpRdTracker::processing); dataBuff->changeState(roxiemem::DBState::queued, roxiemem::DBState::unowned, __func__); collatePacket(dataBuff); } diff --git a/roxie/udplib/udptrs.cpp b/roxie/udplib/udptrs.cpp index c223094c064..fe7c46ae868 100644 --- a/roxie/udplib/udptrs.cpp +++ b/roxie/udplib/udptrs.cpp @@ -80,7 +80,7 @@ RelaxedAtomic flowRequestsSent; RelaxedAtomic flowPermitsReceived; RelaxedAtomic dataPacketsSent; -static unsigned lastResentReport = 0; +static unsigned lastResentReport = msTick(); static unsigned lastOkToSendTimeouts = 0; static unsigned lastPacketsResent = 0; static unsigned lastFlowRequestsSent = 0; diff --git a/system/jlib/jbsocket.cpp b/system/jlib/jbsocket.cpp index 35853588cdd..227f73f628a 100644 --- a/system/jlib/jbsocket.cpp +++ b/system/jlib/jbsocket.cpp @@ -100,7 +100,7 @@ int BufferedSocket::readline(char* buf, int maxlen, bool keepcrlf, IMultiExcepti m_curptr = 0; m_endptr = 0; unsigned readlen; - m_socket->read(m_buf, 0, BSOCKET_BUFSIZE, readlen, m_timeout); + readtmsAllowClose(m_socket, m_buf, 1, BSOCKET_BUFSIZE, readlen, m_timeout*1000); if(readlen > 0) { m_endptr = readlen; @@ -111,7 +111,6 @@ int BufferedSocket::readline(char* buf, int maxlen, bool keepcrlf, IMultiExcepti buf[ptr++] = '\n'; } } - } break; } @@ -136,7 +135,7 @@ int BufferedSocket::readline(char* buf, int maxlen, bool keepcrlf, IMultiExcepti m_curptr = 0; m_endptr = 0; unsigned readlen; - m_socket->read(m_buf, 0, BSOCKET_BUFSIZE, readlen, m_timeout); + readtmsAllowClose(m_socket, m_buf, 1, BSOCKET_BUFSIZE, readlen, m_timeout*1000); if(readlen <= 0) break; m_endptr = readlen; @@ -218,7 +217,7 @@ int BufferedSocket::read(char* buf, int maxlen) unsigned readlen; try { - m_socket->read(m_buf, 0, BSOCKET_BUFSIZE, readlen, m_timeout); + m_socket->read(m_buf, 1, BSOCKET_BUFSIZE, readlen, m_timeout); } catch (IException *e) { diff --git a/system/jlib/jfile.cpp b/system/jlib/jfile.cpp index 0d33fa28a1d..9a62b29801e 100644 --- a/system/jlib/jfile.cpp +++ b/system/jlib/jfile.cpp @@ -6490,7 +6490,7 @@ class CSocketSerialStream: public CSerialStreamBase if (lastpos!=pos) throw MakeStringException(-1,"CSocketSerialStream: non-sequential read (%" I64F "d,%" I64F "d)",lastpos,pos); size32_t size_read; - socket->readtms(ptr, 0, max_size, size_read, timeout); + readtmsAllowClose(socket, ptr, 1, max_size, size_read, timeout); lastpos = pos+size_read; return size_read; } diff --git a/system/jlib/jmisc.hpp b/system/jlib/jmisc.hpp index bc3cdd07205..6e6fe9acafe 100644 --- a/system/jlib/jmisc.hpp +++ b/system/jlib/jmisc.hpp @@ -334,4 +334,107 @@ extern jlib_decl char **getSystemEnv(); extern jlib_decl char *getHPCCEnvVal(const char *name, const char *defaultValue); +// class TimeDivisionTracker is useful for working out what a thread spends its time doing. See udptrrr.cpp for an example +// of its usage + +template class TimeDivisionTracker +{ +protected: + unsigned __int64 totals[NUMSTATES] = {0}; + unsigned counts[NUMSTATES] = {0}; + const char *stateNames[NUMSTATES]; + unsigned __int64 lastTick = get_cycles_now(); + unsigned currentState = 0; + StringAttr name; + unsigned __int64 reportIntervalCycles = 0; + unsigned __int64 lastReport = 0; + + unsigned enterState(unsigned newState) + { + unsigned prevState = currentState; + unsigned __int64 now = get_cycles_now(); + if (reportIntervalCycles && now - lastReport >= reportIntervalCycles) + { + report(true); + now = get_cycles_now(); + } + if (newState != prevState) + { + totals[currentState] += now - lastTick; + currentState = newState; + counts[newState]++; + lastTick = now; + } + return prevState; + } + + void leaveState(unsigned backToState) + { + unsigned __int64 now = get_cycles_now(); + if (reportIntervalCycles && now - lastReport >= reportIntervalCycles) + report(true); + if (backToState != currentState) + { + totals[currentState] += now - lastTick; + lastTick = now; + currentState = backToState; + } + } + +public: + TimeDivisionTracker(const char *_name, unsigned reportIntervalSeconds) : name(_name) + { + if (reportIntervalSeconds) + reportIntervalCycles = millisec_to_cycle(reportIntervalSeconds * 1000); + } + + void report(bool reset) + { + VStringBuffer str("%s spent ", name.str()); + auto now = get_cycles_now(); + totals[currentState] += now - lastTick; + lastTick = now; + lastReport = now; + bool doneOne = false; + for (unsigned i = reportOther ? 0 : 1; i < NUMSTATES; i++) + { + if (counts[i]) + { + if (doneOne) + str.append(", "); + formatTime(str, cycle_to_nanosec(totals[i])); + str.appendf(" %s (%u times)", stateNames[i], counts[i]); + doneOne = true; + } + if (reset) + { + totals[i] = 0; + counts[i] = 0; + } + } + if (doneOne) + DBGLOG("%s", str.str()); + } + + class TimeDivision + { + unsigned prevState = 0; + TimeDivisionTracker &t; + public: + TimeDivision(TimeDivisionTracker &_t, unsigned newState) : t(_t) + { + prevState = t.enterState(newState); + } + ~TimeDivision() + { + t.leaveState(prevState); + } + void switchState(unsigned newState) + { + t.enterState(newState); + } + }; +}; + + #endif diff --git a/system/jlib/jqueue.tpp b/system/jlib/jqueue.tpp index 4f4ed610985..f334469c4dc 100644 --- a/system/jlib/jqueue.tpp +++ b/system/jlib/jqueue.tpp @@ -271,6 +271,8 @@ public: using PARENT::ensure; }; +//Sending signals once the critical section has been released generally gives better performance. +#define SIGNAL_OUTSIDE template class SimpleInterThreadQueueOf : protected SafeQueueOf @@ -358,18 +360,53 @@ public: bool enqueue(BASE *e,unsigned timeout=INFINITE) { - CriticalBlock b(SELF::crit); - if (limit) { - unsigned start=0; - while (limit<=SafeQueueOf::unsafeordinality()) - if (stopped||!qwait(deqwaitsem,deqwaiting,timeout,start)) - return false; + unsigned numToSignal = 0; + { + CriticalBlock b(SELF::crit); + if (limit) { + unsigned start=0; + while (limit<=SafeQueueOf::unsafeordinality()) + if (stopped||!qwait(deqwaitsem,deqwaiting,timeout,start)) + return false; + } + SafeQueueOf::unsafeenqueue(e); +#ifdef SIGNAL_OUTSIDE + numToSignal = enqwaiting; + enqwaiting = 0; +#else + if (enqwaiting) { + enqwaitsem.signal(enqwaiting); + enqwaiting = 0; + } +#endif } - SafeQueueOf::unsafeenqueue(e); - if (enqwaiting) { - enqwaitsem.signal(enqwaiting); + // Signal when critical section no longer held so the reader can actually remove the item + if (numToSignal) + enqwaitsem.signal(numToSignal); + return true; + } + + bool enqueueMany(unsigned num, BASE * *e,unsigned timeout=INFINITE) + { + assertex(!limit); + unsigned numToSignal = 0; + { + CriticalBlock b(SELF::crit); + for (unsigned i=0; i < num; i++) + SafeQueueOf::unsafeenqueue(e[i]); + +#ifdef SIGNAL_OUTSIDE + numToSignal = enqwaiting; enqwaiting = 0; +#else + if (enqwaiting) { + enqwaitsem.signal(enqwaiting); + enqwaiting = 0; + } +#endif } + if (numToSignal) + enqwaitsem.signal(numToSignal); return true; } @@ -409,21 +446,31 @@ public: BASE *dequeue(unsigned timeout=INFINITE) { - CriticalBlock b(SELF::crit); - unsigned start=0; - while (!stopped) { - BASE *ret; - if (get(ret,false)) { - if (deqwaiting) { - deqwaitsem.signal(deqwaiting); + BASE *ret = nullptr; + unsigned numToSignal = 0; + { + CriticalBlock b(SELF::crit); + unsigned start=0; + while (!stopped) { + if (get(ret,false)) { +#ifdef SIGNAL_OUTSIDE + numToSignal = deqwaiting; deqwaiting = 0; +#else + if (deqwaiting) { + deqwaitsem.signal(deqwaiting); + deqwaiting = 0; + } +#endif + break; } - return ret; + if (!qwait(enqwaitsem,enqwaiting,timeout,start)) + break; } - if (!qwait(enqwaitsem,enqwaiting,timeout,start)) - break; } - return NULL; + if (numToSignal) + deqwaitsem.signal(numToSignal); + return ret; } BASE *dequeueTail(unsigned timeout=INFINITE) diff --git a/system/jlib/jsocket.cpp b/system/jlib/jsocket.cpp index 5ecd2369205..d109766bac6 100644 --- a/system/jlib/jsocket.cpp +++ b/system/jlib/jsocket.cpp @@ -24,6 +24,23 @@ look at loopback */ + +/* ISocket implementation/semantic details: + * + * 1. All sockets are created in non-blocking state. All implementations should cope with non-blocking, i.e. wait/retry. + * 2. read/readtms will block until min_size bytes are read or timeout expires (see readtms for more detail). + * read/readtms can be called with min_size=0, in which case it will return as soon as no more data is available. + * 3. Historically, SSL calls + non-SSL write/writetms, used to flip blocking state during the call, this was prone to error, + * because socket could [legitimately] be written to whilst being read from, e.g. a server could be writing to a client, and + * at the same time, be notified of traffic and start reading it. Flipping the blocking state of a socket is inherently unsafe + * in many usage patterns. + * + * The non-blocking semantics are designed so that the select handlers can read as much data as possible without blocking. + * e.g. dafilesrv and MP avoid blocking in their select handlers, thus avoiding up other client traffic. + */ + + + #include #include #include @@ -170,6 +187,7 @@ class jlib_thrown_decl SocketException: public IJSOCK_Exception, public CInterfa case JSOCKERR_handle_too_large: return str.append("handle too large"); case JSOCKERR_bad_netaddr: return str.append("bad net addr"); case JSOCKERR_ipv6_not_implemented: return str.append("IPv6 not implemented"); + case JSOCKERR_small_udp_packet: return str.append("small UDP packet"); // OS errors #ifdef _WIN32 case WSAEINTR: return str.append("WSAEINTR(10004) - Interrupted system call."); @@ -381,35 +399,40 @@ enum SOCKETMODE { sm_tcp_server, sm_tcp, sm_udp_server, sm_udp, sm_multicast_ser # endif #endif -static CriticalSection queryKACS; +enum UseUDE { UNINIT, INITED }; +static std::atomic expertTCPSettings { UNINIT }; +static CriticalSection queryTCPCS; -enum UseKA { UNINIT, DISABLED, ENABLED }; -static std::atomic doKeepAlive { UNINIT }; +static bool hasKeepAlive = false; static int keepAliveTime = -1; static int keepAliveInterval = -1; static int keepAliveProbes = -1; +static bool disableDNSTimeout = false; +static int maxDNSThreads = 50; /* - + global: expert: + disableDNSTimeout: false + maxDNSThreads: 100 keepalive: time: 200 interval: 75 probes: 9 */ -extern jlib_decl bool queryKeepAlive(int &time, int &intvl, int &probes) +static void queryTCPSettings() { - UseKA state = doKeepAlive.load(); + UseUDE state = expertTCPSettings.load(); if (state == UNINIT) { - CriticalBlock block(queryKACS); - state = doKeepAlive.load(); + CriticalBlock block(queryTCPCS); + state = expertTCPSettings.load(); if (state == UNINIT) { #ifdef _CONTAINERIZED @@ -436,7 +459,6 @@ extern jlib_decl bool queryKeepAlive(int &time, int &intvl, int &probes) catch (...) { } - state = DISABLED; if (expert) { IPropertyTree *keepalive = expert->queryPropTree("keepalive"); @@ -445,22 +467,122 @@ extern jlib_decl bool queryKeepAlive(int &time, int &intvl, int &probes) keepAliveTime = keepalive->getPropInt("@time", keepAliveTime); keepAliveInterval = keepalive->getPropInt("@interval", keepAliveInterval); keepAliveProbes = keepalive->getPropInt("@probes", keepAliveProbes); - state = ENABLED; + hasKeepAlive = true; } + disableDNSTimeout = expert->getPropBool("@disableDNSTimeout", false); + maxDNSThreads = expert->getPropInt("@maxDNSThreads", maxDNSThreads); + // could also consider maxDNSThreads==0 as a way to disable DNS timeout ... + if (maxDNSThreads < 1) + maxDNSThreads = 1; } - doKeepAlive = state; + expertTCPSettings = INITED; } } +} - if (state == ENABLED) +extern jlib_decl bool queryKeepAlive(int &time, int &intvl, int &probes) +{ + queryTCPSettings(); + if (hasKeepAlive) { time = keepAliveTime; intvl = keepAliveInterval; probes = keepAliveProbes; + } + return hasKeepAlive; +} + +static int getAddressInfo(const char *name, unsigned *netaddr, bool okToLogErr); + +static CriticalSection queryDNSCS; + +class CAddrInfoThreadArgs : public CInterface +{ +public: + StringAttr name; + unsigned netaddr[4] = { 0, 0, 0, 0 }; + std::atomic retCode { EAI_SYSTEM }; + + CAddrInfoThreadArgs(const char *_name) : name(_name) { } +}; + +class CAddrPoolThread : public CInterface, implements IPooledThread +{ +public: + IMPLEMENT_IINTERFACE; + + Linked localAddrThreadInfo; + + virtual void init(void *param) override + { + localAddrThreadInfo.setown((CAddrInfoThreadArgs *)param); + } + + virtual void threadmain() override + { + localAddrThreadInfo->retCode = getAddressInfo(localAddrThreadInfo->name.get(), localAddrThreadInfo->netaddr, false); + } + + virtual bool stop() override + { return true; } - else - return false; + + virtual bool canReuse() const override + { + return true; + } +}; + +class CAddrInfoFactory : public CInterface, public IThreadFactory +{ +public: + IMPLEMENT_IINTERFACE; + IPooledThread *createNew() + { + return new CAddrPoolThread(); + } +}; + +static Owned addrInfoFactory; +static Owned addrInfoPool; +static std::atomic addrInfoPoolCreated { false }; + +static bool useDNSTimeout() +{ + queryTCPSettings(); + if (!disableDNSTimeout) + { + if (!addrInfoPoolCreated.load()) + { + CriticalBlock block(queryDNSCS); + if (!addrInfoPoolCreated.load()) + { + addrInfoFactory.setown(new CAddrInfoFactory); + addrInfoPool.setown(createThreadPool("AddrInfoPool", addrInfoFactory, true, nullptr, maxDNSThreads, 100000000)); + addrInfoPoolCreated = true; + } + } + return true; + } + return false; +} + +MODULE_INIT(INIT_PRIORITY_STANDARD) +{ + return true; +} + +MODULE_EXIT() +{ + // NB: this (and other MODULE_EXITs) are not called for Thor and Roxie because they are + // stopped via SIGTERM signal and thus exit() and the atexit handlers are not called + if (addrInfoPoolCreated.load()) + { + addrInfoPool->joinAll(true); + addrInfoPool.clear(); + addrInfoPoolCreated = false; + } } struct SocketStats @@ -516,8 +638,8 @@ class CSocket: public ISocket, public CInterface void connect_wait( unsigned timems); void udpconnect(); - void read(void* buf, size32_t min_size, size32_t max_size, size32_t &size_read,unsigned timeoutsecs); - void readtms(void* buf, size32_t min_size, size32_t max_size, size32_t &size_read, unsigned timedelaysecs); + void read(void* buf, size32_t min_size, size32_t max_size, size32_t &size_read, unsigned timeoutsecs, bool suppresGCIfMinSize=true); + void readtms(void* buf, size32_t min_size, size32_t max_size, size32_t &size_read, unsigned timedelaysecs, bool suppresGCIfMinSize=true); void read(void* buf, size32_t size); size32_t write(void const* buf, size32_t size); size32_t writetms(void const* buf, size32_t minSize, size32_t size, unsigned timeoutms=WAIT_FOREVER); @@ -599,6 +721,7 @@ class CSocket: public ISocket, public CInterface if (sock!=INVALID_SOCKET) { T_SOCKET s = sock; sock = INVALID_SOCKET; + nonblocking = false; STATS.activesockets--; #ifdef SOCKTRACE PROGLOG("SOCKTRACE: Closing socket %x %d (%p)", s, s, this); @@ -665,6 +788,7 @@ static win_socket_library ws32_lib; #define JSE_NOTSOCK WSAENOTSOCK #define JSE_TIMEDOUT WSAETIMEDOUT #define JSE_CONNREFUSED WSAECONNREFUSED +#define JSE_EAGAIN WSAEWOULDBLOCK #define JSE_BADF WSAEBADF #define JSE_INTR WSAEINTR @@ -784,6 +908,7 @@ int inet_aton (const char *name, struct in_addr *addr) #define JSE_NOTSOCK ENOTSOCK #define JSE_TIMEDOUT ETIMEDOUT #define JSE_CONNREFUSED ECONNREFUSED +#define JSE_EAGAIN EAGAIN #define JSE_BADF EBADF @@ -886,6 +1011,9 @@ inline void getSockAddrEndpoint(const J_SOCKADDR &u, socklen_t ul, SocketEndpoin bool CSocket::set_nonblock(bool on) { + if (nonblocking==on) + return nonblocking; + int flags = fcntl(sock, F_GETFL, 0); if (flags == -1) return nonblocking; @@ -963,7 +1091,7 @@ size32_t CSocket::avail_read() #define PRE_CONN_UNREACH_ELIM 100 -int CSocket::pre_connect (bool block) +int CSocket::pre_connect(bool block) { if (targetip.isNull()) { @@ -1012,9 +1140,9 @@ int CSocket::pre_connect (bool block) return err; } -int CSocket::post_connect () +int CSocket::post_connect() { - set_nonblock(false); + set_nonblock(true); // normally a NOP, but connect_wait's use of pre_connect could leave it in blocking state int err = 0; socklen_t errlen = sizeof(err); int rc = getsockopt(sock, SOL_SOCKET, SO_ERROR, (char *)&err, &errlen); // check for error @@ -1049,6 +1177,7 @@ void CSocket::open(int listen_queue_size,bool reuseports) } checkCfgKeepAlive(); + set_nonblock(true); STATS.activesockets++; @@ -1140,38 +1269,62 @@ ISocket* CSocket::accept(bool allowcancel, SocketEndpoint *peerEp) socklen_t peerSockAddrLen = sizeof(peerSockAddr); T_SOCKET newsock; - for (;;) { + for (;;) + { in_accept = true; newsock = (sock!=INVALID_SOCKET)?::accept(sock, &peerSockAddr.sa, &peerSockAddrLen):INVALID_SOCKET; in_accept = false; - #ifdef SOCKTRACE +#ifdef SOCKTRACE PROGLOG("SOCKTRACE: accept created socket %x %d (%p)", newsock,newsock,this); - #endif +#endif - if (newsock!=INVALID_SOCKET) { + if (newsock!=INVALID_SOCKET) + { if ((sock==INVALID_SOCKET)||(accept_cancel_state==accept_cancel_pending)) { ::close(newsock); newsock=INVALID_SOCKET; } - else { + else + { accept_cancel_state = accept_not_cancelled; break; } } int saverr; saverr = SOCKETERRNO(); - if ((sock==INVALID_SOCKET)||(accept_cancel_state==accept_cancel_pending)) { + + if ((sock==INVALID_SOCKET)||(accept_cancel_state==accept_cancel_pending)) + { accept_cancel_state = accept_cancelled; if (allowcancel) return NULL; THROWJSOCKTARGETEXCEPTION(JSOCKERR_cancel_accept); } - if (saverr != JSE_INTR) { + else if (saverr == EAGAIN || saverr == EWOULDBLOCK) + { + int rc = wait_read(WAIT_FOREVER); // JCSMORE accept should support a timeout really + if ((sock==INVALID_SOCKET)||(accept_cancel_state==accept_cancel_pending)) + { + accept_cancel_state = accept_cancelled; + if (allowcancel) + return NULL; + THROWJSOCKTARGETEXCEPTION(JSOCKERR_cancel_accept); + } + if (rc < 0) + THROWJSOCKTARGETEXCEPTION(SOCKETERRNO()); + else if (rc == 0) + THROWJSOCKTARGETEXCEPTION(JSOCKERR_timeout_expired); + continue; + } + + if (saverr != JSE_INTR) + { accept_cancel_state = accept_not_cancelled; THROWJSOCKTARGETEXCEPTION(saverr); } } - if (state != ss_open) { + if (state != ss_open) + { accept_cancel_state = accept_cancelled; if (allowcancel) return NULL; @@ -1184,8 +1337,8 @@ ISocket* CSocket::accept(bool allowcancel, SocketEndpoint *peerEp) CSocket *ret = new CSocket(newsock,sm_tcp,true); ret->checkCfgKeepAlive(); ret->set_inherit(false); + ret->set_nonblock(true); return ret; - } @@ -1431,7 +1584,18 @@ inline void refused_sleep(CTimeMon &tm, unsigned &refuseddelay) } } -bool CSocket::connect_timeout( unsigned timeout, bool noexception) +// JCSMORE - there's a lot of duplicated nuanced code here and in connect_wait +// that could do with clearing up. Some of it is very similar to wait_write + +// As they current stand: +// connect_timeout if successful, will always leave the socket in a non-blocking state +// +// connect_wait has weird semantic of trying one final attempt if it has timedout, where it will switch to blocking mode +// NB: that will happen on 1st attempt if connect_wait is passed 0 as a timeout. +// +// pre_connect() is what actually customizes the socket [conditionally] sets it to non-blocking mode +// post_connect() errors checks and ensures it is in non-blocking mode (only relevant if connect_wait left it in blocking mode) +bool CSocket::connect_timeout(unsigned timeout, bool noexception) { // simple connect with timeout (no fancy stuff!) unsigned startt = usTick(); @@ -1441,7 +1605,7 @@ bool CSocket::connect_timeout( unsigned timeout, bool noexception) int err; while (!tm.timedout(&remaining)) { - err = pre_connect(false); + err = pre_connect(false); // NB: sets and leaves socket into non-blocking mode if ((err == JSE_INPROGRESS)||(err == JSE_WOULDBLOCK)) { #ifdef _USE_SELECT @@ -1545,7 +1709,7 @@ void CSocket::connect_wait(unsigned timems) if (++connectingcount>4) blockselect = true; } - err = pre_connect(blockselect); + err = pre_connect(blockselect); if (blockselect) { if (err&&!exit) @@ -1717,6 +1881,7 @@ void CSocket::udpconnect() closesock(); THROWJSOCKTARGETEXCEPTION(JSOCKERR_connection_failed); } + set_nonblock(true); nagling = false; // means nothing for UDP state = ss_open; #ifdef _TRACE @@ -1906,29 +2071,32 @@ int CSocket::wait_write(unsigned timeout) return ret; } -void CSocket::readtms(void* buf, size32_t min_size, size32_t max_size, size32_t &sizeRead, unsigned timeoutMs) +void CSocket::readtms(void* buf, size32_t min_size, size32_t max_size, size32_t &sizeRead, unsigned timeoutMs, bool suppresGCIfMinSize) { + /* + * Read at least min_size bytes, up to max_size bytes. + * Reads as much as possible off socket until block detected. + * NB: If min_size==0 then will return asap if no data is avail. + * NB: If min_size==0, but notified of graceful close, throw graceful close exception. + * NB: timeout is meaningless if min_size is 0 + * + * NB: for UDP, it will never try to read more. It will call recvfrom once, if it gets less than min_size, it will throw an exception. + * if blocks and received nothing (and min_size>0), poll and retry. + * + * NB: the underlying socket should be in non-blocking mode, if it is not it will not honour the timeout correctly. + */ + sizeRead = 0; if (0 == max_size) - { return; - } if (state != ss_open) THROWJSOCKEXCEPTION(JSOCKERR_not_opened); - // NB: The semantics here, effectively mean min_size is always >0, because it first waits on wait_read - // i.e. something has to be on socket to continue (or error/graceful close). CCycleTimer timer; while (true) { - unsigned remainingMs = timer.remainingMs(timeoutMs); - int rc = wait_read(remainingMs); - if (rc < 0) - THROWJSOCKTARGETEXCEPTION(SOCKETERRNO()); - else if (rc == 0) - THROWJSOCKTARGETEXCEPTION(JSOCKERR_timeout_expired); - unsigned retrycount=100; + int rc; EintrRetry: if (sockmode==sm_udp_server) // udp server { @@ -1939,12 +2107,18 @@ void CSocket::readtms(void* buf, size32_t min_size, size32_t max_size, size32_t } else rc = recv(sock, (char*)buf + sizeRead, max_size - sizeRead, 0); - if (rc > 0) { sizeRead += rc; - if (sizeRead >= min_size) + if (sockmode==sm_udp_server) + { + if (sizeRead >= min_size) + break; + throwJSockException(JSOCKERR_small_udp_packet, "readtms: UDP packet smaller than min_size", __FILE__, __LINE__); // else, it is an error in UDP if receive anything less than min_size + } + else if (sizeRead == max_size) break; + // NB: will exit when blocked if sizeRead >= min_size } else { @@ -1969,11 +2143,13 @@ void CSocket::readtms(void* buf, size32_t min_size, size32_t max_size, size32_t } else { - if (nonblocking && (err == JSE_WOULDBLOCK || err == EAGAIN)) // if EGAIN or EWOULDBLOCK - no more data to read + if (err == JSE_WOULDBLOCK || err == JSE_EAGAIN) // if EAGAIN or EWOULDBLOCK - no more data to read { - if (0 == min_size) // if here, implies nothing read, since it would have exited already in (rc > 0) block. + //NB: in UDP can only reach here if have not read anything so far. + + if (sizeRead >= min_size) break; - // fall through/loop around. NB: rc != 0 + // otherwise, continue waiting for min_size } else { @@ -1986,16 +2162,40 @@ void CSocket::readtms(void* buf, size32_t min_size, size32_t max_size, size32_t } THROWJSOCKTARGETEXCEPTION(err); } + // fall through to timeout/wait_read handling below. } } if (rc == 0) { state = ss_shutdown; - if (sizeRead >= min_size) - break; // suppress graceful close exception if have already read minimum + if (suppresGCIfMinSize && (sizeRead >= min_size)) + break; THROWJSOCKTARGETEXCEPTION(JSOCKERR_graceful_close); } } + + unsigned remainingMs = timer.remainingMs(timeoutMs); + if (rc > 0) + { + if (0 == remainingMs) + { + if (sizeRead >= min_size) + break; + THROWJSOCKTARGETEXCEPTION(JSOCKERR_timeout_expired); + } + + // loop around to read more, or detect blocked. + } + else // NB rc < 0, (if rc == 0 handled already above) + { + // here because blocked (and sizeRead < min_size) + rc = wait_read(remainingMs); + if (rc < 0) + THROWJSOCKTARGETEXCEPTION(SOCKETERRNO()); + else if (rc == 0) + THROWJSOCKTARGETEXCEPTION(JSOCKERR_timeout_expired); + } + //else // read something, loop around to see if can read more, or detect blocked. } cycle_t elapsedCycles = timer.elapsedCycles(); @@ -2007,10 +2207,26 @@ void CSocket::readtms(void* buf, size32_t min_size, size32_t max_size, size32_t stats.ioReadCycles += elapsedCycles; } -void CSocket::read(void* buf, size32_t min_size, size32_t max_size, size32_t &size_read, unsigned timeoutsecs) +void CSocket::read(void* buf, size32_t min_size, size32_t max_size, size32_t &size_read, unsigned timeoutsecs, bool suppresGCIfMinSize) { unsigned timeoutMs = (timeoutsecs==WAIT_FOREVER) ? WAIT_FOREVER : (timeoutsecs * 1000); - readtms(buf, min_size, max_size, size_read, timeoutMs); + readtms(buf, min_size, max_size, size_read, timeoutMs, suppresGCIfMinSize); +} + +bool readtmsAllowClose(ISocket *sock, void* buf, size32_t min_size, size32_t max_size, size32_t &sizeRead, unsigned timeoutMs) +{ + try + { + sock->readtms(buf, min_size, max_size, sizeRead, timeoutMs, false); + } + catch(IJSOCK_Exception *e) + { + if (JSOCKERR_graceful_close != e->errorCode()) + throw; + e->Release(); + return true; + } + return false; } void CSocket::read(void* buf, size32_t size) @@ -2028,17 +2244,6 @@ size32_t CSocket::writetms(void const* buf, size32_t minSize, size32_t size, uns if (state != ss_open) THROWJSOCKTARGETEXCEPTION(JSOCKERR_not_opened); - // If timeoutMs != WAIT_FOREVER, set non-blocking mode for the duration of this function - struct ScopedNonBlockingMode - { - CSocket *socket = nullptr; - bool prevMode = false; - void init(CSocket *_socket) { socket = _socket; prevMode = socket->set_nonblock(true); } - ~ScopedNonBlockingMode() { if (socket) socket->set_nonblock(prevMode); } - } scopedNonBlockingMode; - - if (WAIT_FOREVER != timeoutMs) - scopedNonBlockingMode.init(this); while (true) { unsigned retrycount=100; @@ -2064,8 +2269,9 @@ size32_t CSocket::writetms(void const* buf, size32_t minSize, size32_t size, uns if (rc > 0) { sizeWritten += rc; - if (sizeWritten >= minSize) + if (sizeWritten == size) break; + // NB: will exit when blocked if sizeWritten >= minSize } else if (rc < 0) { @@ -2094,8 +2300,10 @@ size32_t CSocket::writetms(void const* buf, size32_t minSize, size32_t size, uns errclose(); err = JSOCKERR_broken_pipe; } - if ((err == JSE_WOULDBLOCK) && nonblocking) + if (err == JSE_WOULDBLOCK || err == JSE_EAGAIN) { + if (sizeWritten >= minSize) + break; unsigned remainingMs = timer.remainingMs(timeoutMs); rc = wait_write(remainingMs); if (rc < 0) @@ -2219,7 +2427,6 @@ size32_t CSocket::udp_write_to(const SocketEndpoint &ep, void const* buf, size32 size32_t CSocket::write_multiple(unsigned num,const void **buf, size32_t *size) { assertex(sockmode!=sm_udp_server); - assertex(!nonblocking); if (num==1) return write(buf[0],size[0]); size32_t total = 0; @@ -2559,7 +2766,6 @@ void CSocket::shutdown(unsigned mode) void CSocket::shutdownNoThrow(unsigned mode) { if (state == ss_open) { - state = ss_shutdown; #ifdef SOCKTRACE PROGLOG("SOCKTRACE: shutdown(%d) socket %x %d (%p)", mode, sock, sock, this); #endif @@ -2937,6 +3143,7 @@ ISocket* ISocket::multicast_connect(const SocketEndpoint &ep, unsigned _ttl) ISocket* ISocket::attach(int s, bool tcpip) { CSocket* sock = new CSocket((SOCKET)s, tcpip?sm_tcp:sm_udp, false); + sock->set_nonblock(true); return sock; } @@ -3313,82 +3520,57 @@ static bool decodeNumericIP(const char *text,unsigned *netaddr) return false; } -static bool lookupHostAddress(const char *name,unsigned *netaddr) +static void RecursionSafeLogErr(int ret, int ref, const char *msg, unsigned lineno, const char *name) { - // if IP4only or using MS V6 can only resolve IPv4 using - static bool recursioncheck = false; // needed to stop error message recursing - unsigned retry=10; -#if defined(__linux__) || defined (__APPLE__) || defined(getaddrinfo) - if (IP4only) { -#else + static __thread bool recursioncheck = false; // needed to stop error message recursing + if (!recursioncheck) { + recursioncheck = true; + LogErr(ret, ref, msg, lineno, name); +#ifdef _DEBUG + PrintStackReport(); #endif - CriticalBlock c(hostnamesect); - hostent * entry = gethostbyname(name); - while (entry==NULL) { - if (retry--==0) { - if (!recursioncheck) { - recursioncheck = true; - LogErr(h_errno,1,"gethostbyname failed",__LINE__,name); - recursioncheck = false; - } - return false; - } - { - CriticalUnblock ub(hostnamesect); - Sleep((10-retry)*100); - } - entry = gethostbyname(name); - } - if (entry->h_addr_list[0]) { - unsigned ptr = 0; - if (!PreferredSubnet.isNull()) { - for (;;) { - ptr++; - if (entry->h_addr_list[ptr]==NULL) { - ptr = 0; - break; - } - IpAddress ip; - ip.setNetAddress(sizeof(unsigned),entry->h_addr_list[ptr]); - if (PreferredSubnet.test(ip)) - break; - } - } - memcpy(&netaddr[3], entry->h_addr_list[ptr], sizeof(netaddr[3])); - netaddr[2] = 0xffff0000; - netaddr[1] = 0; - netaddr[0] = 0; - return true; - } - return false; + recursioncheck = false; } -#if defined(__linux__) || defined (__APPLE__) || defined(getaddrinfo) +} + +int getAddressInfo(const char *name, unsigned *netaddr, bool okToLogErr) +{ struct addrinfo hints; - memset(&hints,0,sizeof(hints)); - struct addrinfo *addrInfo = NULL; - for (;;) { - memset(&hints,0,sizeof(hints)); + struct addrinfo *addrInfo = NULL; + int retry=10; + int retCode; + + // each retry could take up to several seconds, depending on how DNS resolver is configured + // should a few specific non-zero return codes break out early from retry loop (EAI_NONAME) ? + while (true) + { + memset(&hints, 0, sizeof(hints)); // dont wait for both A and AAAA records ... if (IP4only || (!IP6preferred)) hints.ai_family = AF_INET; - int ret = getaddrinfo(name, NULL , &hints, &addrInfo); - if (!ret) + // hints.ai_flags = AI_CANONNAME | AI_ADDRCONFIG | AI_V4MAPPED + retCode = getaddrinfo(name, NULL, &hints, &addrInfo); + if (0 == retCode) break; - if (retry--==0) { - if (!recursioncheck) { - recursioncheck = true; - LogErr(ret,1,"getaddrinfo failed",__LINE__,name); -#ifdef _DEBUG - PrintStackReport(); -#endif - recursioncheck = false; - } - return false; + if (--retry > 0) + Sleep((10-retry)*100); + else + { + // use gai_strerror(ret) to get meaningful error text ? + if (okToLogErr) + RecursionSafeLogErr(retCode, 1, "getaddrinfo failed", __LINE__, name); + return retCode; + } + if (EAI_NONAME == retCode) + { + // try one more time, but why ? + // MCK TODO: probably should only retry on EAI_AGAIN and possibly EAI_SYSTEM ... + retry = 1; } - Sleep((10-retry)*100); } - struct addrinfo *best = NULL; + + struct addrinfo *best = NULL; bool snm = !PreferredSubnet.isNull(); for (;;) { struct addrinfo *ai; @@ -3416,7 +3598,7 @@ static bool lookupHostAddress(const char *name,unsigned *netaddr) if ((best==NULL)||((best->ai_family==AF_INET)&&IP6preferred)) best = ai; break; - } + } } } if (best||!snm) @@ -3434,15 +3616,131 @@ static bool lookupHostAddress(const char *name,unsigned *netaddr) } } freeaddrinfo(addrInfo); - return best!=NULL; -#endif - return false; - + if (best!=NULL) + return 0; + else + return EAI_NONAME; // or EAI_NODATA ? } +static bool lookupHostAddress(const char *name, unsigned *netaddr, unsigned timeoutms=INFINITE) +{ + // if IP4only or using MS V6 can only resolve IPv4 using + int retry=10; +#if defined(__linux__) || defined (__APPLE__) || defined(getaddrinfo) + if (IP4only) { +#else + { +#endif + CriticalBlock c(hostnamesect); + hostent * entry = gethostbyname(name); + while (entry==NULL) { + if (retry--==0) { + RecursionSafeLogErr(h_errno, 1, "gethostbyname failed", __LINE__, name); + return false; + } + { + CriticalUnblock ub(hostnamesect); + Sleep((10-retry)*100); + } + entry = gethostbyname(name); + } + if (entry->h_addr_list[0]) { + unsigned ptr = 0; + if (!PreferredSubnet.isNull()) { + for (;;) { + ptr++; + if (entry->h_addr_list[ptr]==NULL) { + ptr = 0; + break; + } + IpAddress ip; + ip.setNetAddress(sizeof(unsigned),entry->h_addr_list[ptr]); + if (PreferredSubnet.test(ip)) + break; + } + } + memcpy(&netaddr[3], entry->h_addr_list[ptr], sizeof(netaddr[3])); + netaddr[2] = 0xffff0000; + netaddr[1] = 0; + netaddr[0] = 0; + return true; + } + return false; + } + +#if defined(__linux__) || defined (__APPLE__) || defined(getaddrinfo) + int retCode = 0; + if ( (timeoutms != INFINITE) && (useDNSTimeout()) ) // could addrInfoPool be NULL ? + { + // getaddrinfo_a() offers an async getaddrinfo method, but has some limitations and a possible mem leak + // could implement timeout getaddrinfo functionality without threads by connecting with DNS servers and parsing response ... + // lookup performance may be significantly improved if system uses a DNS resolver cache + + PooledThreadHandle thrdHandle; + Owned addrThreadInfo = new CAddrInfoThreadArgs(name); + CTimeMon dnstimeout(timeoutms); + + try + { + // wait up to timeoutms for an available thread from pool ... + thrdHandle = addrInfoPool->start((void *)addrThreadInfo.getLink(), "getaddrinfo-thread", timeoutms); + // MCK TODO: if have to create a new thread and at os/system/shell thread limit, create/start can take up to several seconds, can we control this ? + } + catch (IException *e) + { + addrThreadInfo->Release(); + StringBuffer excMsg; + StringBuffer emsg; + emsg.appendf("getaddrinfo failed (thread) (%d) (exc:%d) %s", timeoutms, e->errorCode(), e->errorMessage(excMsg).str()); + RecursionSafeLogErr(100, 1, emsg.str(), __LINE__, name); + e->Release(); + return false; + } + catch (...) + { + addrThreadInfo->Release(); + RecursionSafeLogErr(101, 1, "getaddrinfo failed (thread) (other exc)", __LINE__, name); + return false; + } -bool IpAddress::ipset(const char *text) + // take into account time already passed creating/starting thread above ... + unsigned remaining; + dnstimeout.timedout(&remaining); + if (addrInfoPool->join(thrdHandle, remaining)) + { + if (0 == addrThreadInfo->retCode) + { + memcpy(netaddr, addrThreadInfo->netaddr, sizeof(addrThreadInfo->netaddr)); + return true; + } + else + { + // use gai_strerror(ret) to get meaningful error text ? + StringBuffer emsg; + emsg.appendf("getaddrinfo failed (thread)"); + RecursionSafeLogErr(addrThreadInfo->retCode.load(), 1, emsg.str(), __LINE__, name); + return false; + } + } + // if join() returns false thread still running, but its detached, + // will terminate, release thread args and return to pool on its own ... + + StringBuffer emsg; + emsg.appendf("getaddrinfo timed out (thread) (%d)", timeoutms); + RecursionSafeLogErr(EAI_AGAIN, 1, emsg.str(), __LINE__, name); + return false; + } + + retCode = getAddressInfo(name, netaddr, true); + if (0 == retCode) + return true; + else + return false; +#endif +} + +bool IpAddress::ipset(const char *text, unsigned timeoutms) { if (text&&*text) { @@ -3462,7 +3760,7 @@ bool IpAddress::ipset(const char *text) break; if (!*s) return ipset(NULL); - if (lookupHostAddress(text,netaddr)) + if (lookupHostAddress(text, netaddr, timeoutms)) { hostname.set(text); return true; @@ -3669,7 +3967,7 @@ void SocketEndpoint::serialize(MemoryBuffer & out) const } -bool SocketEndpoint::set(const char *name,unsigned short _port) +bool SocketEndpoint::set(const char *name,unsigned short _port, unsigned timeoutms) { if (name) { if (*name=='[') { @@ -3699,7 +3997,7 @@ bool SocketEndpoint::set(const char *name,unsigned short _port) name = ips; _port = atoi(colon+1); } - if (ipset(name)) { + if (ipset(name, timeoutms)) { port = _port; return true; } @@ -6432,44 +6730,15 @@ bool SocketEndpointArray::fromName(const char *name, unsigned defport) return ordinality()>0; } #if defined(__linux__) || defined (__APPLE__) || defined(getaddrinfo) - struct addrinfo hints; - memset(&hints,0,sizeof(hints)); - struct addrinfo *addrInfo = NULL; - memset(&hints,0,sizeof(hints)); - int ret = getaddrinfo(name, NULL , &hints, &addrInfo); - if (ret == 0) + unsigned netaddr[4]; + int retCode = getAddressInfo(name, netaddr, true); + if (0 == retCode) { - struct addrinfo *ai; - for (ai = addrInfo; ai; ai = ai->ai_next) - { - // DBGLOG("flags=%d, family=%d, socktype=%d, protocol=%d, addrlen=%d, canonname=%s",ai->ai_flags,ai->ai_family,ai->ai_socktype,ai->ai_protocol,ai->ai_addrlen,ai->ai_canonname?ai->ai_canonname:"NULL"); - if (ai->ai_protocol == IPPROTO_IP) - { - switch (ai->ai_family) - { - case AF_INET: - { - SocketEndpoint ep; - ep.setNetAddress(sizeof(in_addr),&(((sockaddr_in *)ai->ai_addr)->sin_addr)); - ep.port = defport; - append(ep); - // StringBuffer s; - // DBGLOG("Lookup %s found %s", name, ep.getEndpointHostText(s).str()); - break; - } - case AF_INET6: - { - SocketEndpoint ep; - ep.setNetAddress(sizeof(in_addr6),&(((sockaddr_in6 *)ai->ai_addr)->sin6_addr)); - ep.port = defport; - append(ep); - break; - } - } - } - } + SocketEndpoint ep; + ep.setNetAddress(sizeof(netaddr),netaddr); + ep.port = defport; + append(ep); } - freeaddrinfo(addrInfo); #endif return ordinality()>0; } diff --git a/system/jlib/jsocket.hpp b/system/jlib/jsocket.hpp index 8b82a1d9d8e..08524d55b72 100644 --- a/system/jlib/jsocket.hpp +++ b/system/jlib/jsocket.hpp @@ -56,9 +56,10 @@ enum JSOCKET_ERROR_CODES { JSOCKERR_cancel_accept = -8, // accept JSOCKERR_connectionless_socket = -9, // accept, cancel_accept JSOCKERR_graceful_close = -10, // read,send - JSOCKERR_handle_too_large = -11, // select, connect etc (linux only) + JSOCKERR_handle_too_large = -11, // select, connect etc (linux only) JSOCKERR_bad_netaddr = -12, // get/set net address - JSOCKERR_ipv6_not_implemented = -13 // various + JSOCKERR_ipv6_not_implemented = -13, // various + JSOCKERR_small_udp_packet = -14 // small udp packet }; // Block operation flags @@ -94,7 +95,7 @@ class jlib_decl IpAddress IpAddress() = default; explicit IpAddress(const char *text) { ipset(text); } - bool ipset(const char *text); // sets to NULL if fails or text=NULL + bool ipset(const char *text, unsigned timeoutms=INFINITE); // sets to NULL if fails or text=NULL void ipset(const IpAddress& other) { *this = other; } bool ipequals(const IpAddress & other) const; int ipcompare(const IpAddress & other) const; // depreciated @@ -153,7 +154,7 @@ class jlib_decl SocketEndpoint : extends IpAddress { public: SocketEndpoint() = default; - SocketEndpoint(const char *name,unsigned short _port=0) { set(name,_port); }; + SocketEndpoint(const char *name,unsigned short _port=0, unsigned timeoutms=INFINITE) { set(name,_port,timeoutms); }; SocketEndpoint(unsigned short _port) { setLocalHost(_port); }; SocketEndpoint(unsigned short _port, const IpAddress & _ip) { set(_port,_ip); }; SocketEndpoint(const SocketEndpoint &other) = default; @@ -161,7 +162,7 @@ class jlib_decl SocketEndpoint : extends IpAddress void deserialize(MemoryBuffer & in); void serialize(MemoryBuffer & out) const; - bool set(const char *name,unsigned short _port=0); + bool set(const char *name,unsigned short _port=0, unsigned timeoutms=INFINITE); inline void set(const SocketEndpoint & value) { ipset(value); port = value.port; } inline void setLocalHost(unsigned short _port) { port = _port; GetHostIp(*this); } // NB *not* localhost(127.0.0.1) inline void set(unsigned short _port, const IpAddress & _ip) { ipset(_ip); port = _port; }; @@ -230,7 +231,6 @@ class jlib_decl IpSubNet } }; - class jlib_decl ISocket : extends IInterface { public: @@ -292,13 +292,15 @@ class jlib_decl ISocket : extends IInterface // static ISocket* attach(int s,bool tcpip=true); - + // suppresGCIfMinSize - if true, will suppress graceful close if size_read >= min_size + // This is the default behavior for backwards compatibility. + // Set to false, to allow caller to see graceful close even if size_read >= min_size virtual void read(void* buf, size32_t min_size, size32_t max_size, size32_t &size_read, - unsigned timeoutsecs = WAIT_FOREVER) = 0; + unsigned timeoutsecs = WAIT_FOREVER, bool suppresGCIfMinSize = true) = 0; virtual void readtms(void* buf, size32_t min_size, size32_t max_size, size32_t &size_read, - unsigned timeout) = 0; + unsigned timeout, bool suppresGCIfMinSize = true) = 0; virtual void read(void* buf, size32_t size) = 0; - virtual size32_t write(void const* buf, size32_t size) = 0; // returns amount written normally same as in size (see set_nonblock) + virtual size32_t write(void const* buf, size32_t size) = 0; virtual size32_t writetms(void const* buf, size32_t minSize, size32_t size, unsigned timeoutms=WAIT_FOREVER) = 0; virtual size32_t get_max_send_size() = 0; @@ -459,6 +461,10 @@ Exceptions raised: (when set_raise_exceptions(TRUE)) }; +// helper function that allows a graceful close on a readtms to return with less than min_size. +// A common pattern is to read >=1 byte(s), but allow graceful close to return less (e.g. 0) +// NB: returns true if graceful close detected during read +extern jlib_decl bool readtmsAllowClose(ISocket *sock, void* buf, size32_t min_size, size32_t max_size, size32_t &sizeRead, unsigned timeoutMs); interface jlib_thrown_decl IJSOCK_Exception: extends IException { @@ -743,6 +749,7 @@ class jlib_decl CSingletonSocketConnection: implements IConversation, public CIn extern jlib_decl void shutdownAndCloseNoThrow(ISocket * optSocket); // Safely shutdown and close a socket without throwing an exception. + #ifdef _WIN32 #define SOCKETERRNO() WSAGetLastError() #else diff --git a/system/jlib/jstats.cpp b/system/jlib/jstats.cpp index ac6352ddc80..0dda1b137a5 100644 --- a/system/jlib/jstats.cpp +++ b/system/jlib/jstats.cpp @@ -197,7 +197,7 @@ const static unsigned __int64 oneMinute = I64C(60000000000); const static unsigned __int64 oneHour = I64C(3600000000000); const static unsigned __int64 oneDay = 24 * I64C(3600000000000); -static void formatTime(StringBuffer & out, unsigned __int64 value) +void formatTime(StringBuffer & out, unsigned __int64 value) { //Aim to display at least 3 significant digits in the result string if (value < oneMicroSecond) diff --git a/system/jlib/jstats.h b/system/jlib/jstats.h index 253d640c94d..14414235266 100644 --- a/system/jlib/jstats.h +++ b/system/jlib/jstats.h @@ -23,6 +23,7 @@ #include "jmutex.hpp" #include #include +#include #include "jstatcodes.h" @@ -44,6 +45,8 @@ inline constexpr stat_type statPercentageOf(stat_type value, stat_type per) { re inline StatisticKind queryStatsVariant(StatisticKind kind) { return (StatisticKind)(kind & ~StKindMask); } inline cost_type money2cost_type(double money) { return money * 1E6; } inline double cost_type2money(cost_type cost) { return ((double) cost) / 1E6; } + +extern jlib_decl void formatTime(StringBuffer & out, unsigned __int64 value); //--------------------------------------------------------------------------------------------------------------------- //Represents a single level of a scope @@ -838,6 +841,21 @@ void mergeStat(CRuntimeStatisticCollection & stats, INTERFACE * source, Statisti template void mergeStat(CRuntimeStatisticCollection & stats, const Shared & source, StatisticKind kind) { mergeStat(stats, source.get(), kind); } +// helper templates that add delta of previous vs current (from source) to tgtStats (and update prevStats) +template +void updateStatsDelta(CRuntimeStatisticCollection & tgtStats, CRuntimeStatisticCollection & prevStats, INTERFACE * source) +{ + CRuntimeStatisticCollection curStats(tgtStats.queryMapping()); + mergeStats(curStats, source); + prevStats.updateDelta(tgtStats, curStats); // NB: adds delta to tgtStats, and updates prevStats +} + +template +void updateStatsDelta(CRuntimeStatisticCollection & tgtStats, CRuntimeStatisticCollection & prevStats, const Shared & source) +{ + updateStatsDelta(tgtStats, prevStats, source.get()); +} + //Some template helper classes for overwriting/setting statistics from external sources. @@ -874,6 +892,55 @@ void setStat(CRuntimeStatisticCollection & stats, INTERFACE * source, StatisticK template void setStat(CRuntimeStatisticCollection & stats, const Shared & source, StatisticKind kind) { setStat(stats, source.get(), kind); } + +typedef std::map StatKindMap; + +template +void mergeRemappedStats(CRuntimeStatisticCollection & stats, INTERFACE * source, const StatisticsMapping & mapping, const StatKindMap & remaps) +{ + if (!source) + return; + unsigned max = mapping.numStatistics(); + for (unsigned i=0; i < max; i++) + { + StatisticKind kind = mapping.getKind(i); + if (remaps.find(kind) == remaps.end()) + stats.mergeStatistic(kind, source->getStatistic(kind)); + } + for (auto remap: remaps) + { + if (mapping.hasKind(remap.second)) + stats.mergeStatistic(remap.second, source->getStatistic(remap.first)); + } +} + +template +void mergeRemappedStats(CRuntimeStatisticCollection & stats, INTERFACE * source, const StatKindMap & remaps) +{ + mergeRemappedStats(stats, source, stats.queryMapping(), remaps); +} + +template +void mergeRemappedStats(CRuntimeStatisticCollection & stats, const Shared & source, const StatKindMap & remaps) +{ + mergeRemappedStats(stats, source.get(), stats.queryMapping(), remaps); +} + +template +void updateRemappedStatsDelta(CRuntimeStatisticCollection & tgtStats, CRuntimeStatisticCollection & prevStats, INTERFACE * source, const StatKindMap & remap) +{ + CRuntimeStatisticCollection curStats(tgtStats.queryMapping()); + ::mergeRemappedStats(curStats, source, remap); + prevStats.updateDelta(tgtStats, curStats); // NB: adds delta to tgtStats, and updates prevStats +} + +template +void updateRemappedStatsDelta(CRuntimeStatisticCollection & tgtStats, CRuntimeStatisticCollection & prevStats, const Shared & source, const StatKindMap & remap) +{ + updateRemappedStatsDelta(tgtStats, prevStats, source.get(), remap); +} + + //--------------------------------------------------------------------------------------------------------------------- //A class for minimizing the overhead of collecting timestamps. diff --git a/system/jlib/jtrace.hpp b/system/jlib/jtrace.hpp index c7b18595b22..85b6e3bb915 100644 --- a/system/jlib/jtrace.hpp +++ b/system/jlib/jtrace.hpp @@ -325,6 +325,7 @@ constexpr TraceFlags traceNone = TraceFlags::None; constexpr TraceFlags traceStandard = TraceFlags::Standard; constexpr TraceFlags traceDetailed = TraceFlags::Detailed; constexpr TraceFlags traceMax = TraceFlags::Max; +constexpr TraceFlags traceAll = (TraceFlags)(~TraceFlags::LevelMask); // i.e. all feature flags except for the detail level // Common to several engines constexpr TraceFlags traceHttp = TraceFlags::flag1; @@ -335,6 +336,7 @@ constexpr TraceFlags traceCouchbase = TraceFlags::flag5; constexpr TraceFlags traceFilters = TraceFlags::flag6; constexpr TraceFlags traceKafka = TraceFlags::flag7; constexpr TraceFlags traceJava = TraceFlags::flag8; +constexpr TraceFlags traceOptimizations = TraceFlags::flag9; // code generator, but IHqlExpressions also used by esp/engines // Specific to Roxie constexpr TraceFlags traceRoxieLock = TraceFlags::flag16; @@ -353,7 +355,8 @@ constexpr TraceFlags traceSmartStepping = TraceFlags::flag28; constexpr TraceFlags traceAborts = TraceFlags::flag29; constexpr TraceFlags traceAcknowledge = TraceFlags::flag30; - +//Specific to the code generator +// see traceOptimizations above. //========================================================================================= @@ -394,6 +397,16 @@ constexpr std::initializer_list roxieTraceOptions TRACEOPT(traceAcknowledge), }; +constexpr std::initializer_list eclccTraceOptions +{ + TRACEOPT(traceNone), + TRACEOPT(traceAll), // place before the other options so you can enable all and selectively disable + TRACEOPT(traceStandard), + TRACEOPT(traceDetailed), + TRACEOPT(traceMax), + TRACEOPT(traceOptimizations), +}; + interface IPropertyTree; extern jlib_decl bool doTrace(TraceFlags featureFlag, TraceFlags level=TraceFlags::Standard); diff --git a/system/jlib/jutil.cpp b/system/jlib/jutil.cpp index d5c8acb8917..cab3860fbb2 100644 --- a/system/jlib/jutil.cpp +++ b/system/jlib/jutil.cpp @@ -3595,3 +3595,16 @@ extern jlib_decl void getResourceFromJfrog(StringBuffer &localPath, IPropertyTre throw makeStringExceptionV(0, "MD5 mismatch on file %s in manifest", filename.str()); } } + +void hold(const char *msg) +{ + WARNLOG("Holding: %s", msg); + bool held = true; + while (held) + { + MilliSleep(5000); + } + WARNLOG("Released: %s", msg); +} + + diff --git a/system/jlib/jutil.hpp b/system/jlib/jutil.hpp index 3bbe204ac15..9e3cf4bfcc4 100644 --- a/system/jlib/jutil.hpp +++ b/system/jlib/jutil.hpp @@ -661,5 +661,7 @@ extern jlib_decl bool getDefaultPlane(StringBuffer &ret, const char * componentO extern jlib_decl void getResourceFromJfrog(StringBuffer &localPath, IPropertyTree &item); +extern jlib_decl void hold(const char *msg); + #endif diff --git a/system/mp/mpcomm.cpp b/system/mp/mpcomm.cpp index 8d9f59ac2cc..d9f1691ef8b 100644 --- a/system/mp/mpcomm.cpp +++ b/system/mp/mpcomm.cpp @@ -26,6 +26,8 @@ #include #include +#include +#include #include "platform.h" #include "portlist.h" @@ -75,8 +77,8 @@ #define CONNECT_TIMEOUT_MINSLEEP 2000 // random range: CONNECT_TIMEOUT_MINSLEEP to CONNECT_TIMEOUT_MAXSLEEP milliseconds #define CONNECT_TIMEOUT_MAXSLEEP 5000 -#define CONFIRM_TIMEOUT (90*1000) // 1.5 mins -#define CONFIRM_TIMEOUT_INTERVAL 5000 // 5 secs +#define PING_CONFIRM_TIMEOUT (90*1000) // 1.5 mins +#define HEADER_CONFIRM_TIMEOUT (90*1000) // 1.5 mins #define TRACESLOW_THRESHOLD 1000 // 1 sec #define VERIFY_DELAY (1*60*1000) // 1 Minute @@ -441,97 +443,337 @@ class CMPServer; class CMPChannel; -class CMPConnectThread: public Thread +static CriticalSection portProbeCS; +static cycle_t portProbeLastLog = 0; +enum CloseType { CloseType_graceful, CloseType_error, CloseType_timeout, CloseType_COUNT }; +static std::array portProbeCloseCounts = {}; +static std::array portProbeCloseCycles = {}; +static cycle_t oneMinCycles = 0; // initialized in trackPortProbe 1st time + + +static void trackPortProbe(cycle_t createTimeCycles, const char *peerEndpointTest, CloseType closeType) { - std::atomic running; - bool listen; - ISocket *listensock; - CMPServer *parent; - int mpSoMaxConn; - unsigned acceptThreadPoolSize = 0; - Owned threadPool; + // this should be based on a logging feature flag + cycle_t nowCycles = get_cycles_now(); + cycle_t elapsedCycles = nowCycles - createTimeCycles; + CLeavableCriticalBlock b(portProbeCS); + portProbeCloseCounts[closeType]++; + portProbeCloseCycles[closeType] += elapsedCycles; + if ((0 == portProbeLastLog)) + { + portProbeLastLog = nowCycles; + oneMinCycles = queryOneSecCycles()*60; + } + else + { + cycle_t cyclesSinceLastLog = nowCycles - portProbeLastLog; + if (cyclesSinceLastLog >= oneMinCycles) // log max every minute + { + unsigned __int64 numGracefulClose = portProbeCloseCounts[CloseType_graceful]; + unsigned __int64 numError = portProbeCloseCounts[CloseType_error]; + unsigned __int64 numTimeout = portProbeCloseCounts[CloseType_timeout]; + cycle_t cyclesGracefulClose = portProbeCloseCycles[CloseType_graceful]; + cycle_t cyclesError = portProbeCloseCycles[CloseType_error]; + cycle_t cyclesTimeout = portProbeCloseCycles[CloseType_timeout]; - Owned allowListCallback; - void checkSelfDestruct(void *p,size32_t sz); + portProbeLastLog = nowCycles; - Owned secureContextServer; + b.leave(); // leave crit before logging - class CSlowClientProcessor : implements IThreaded + unsigned __int64 totalProbes = numGracefulClose + numError + numTimeout; + DBGLOG("Port probes: %" I64F "u [graceful=%" I64F "u (%" I64F "u ms),error=%" I64F "u (%" I64F "u ms),timedout=%" I64F "u (%" I64F "u ms). Last: %s, type=%s, time: %" I64F "u", + totalProbes, + numGracefulClose, cycle_to_millisec(cyclesGracefulClose), + numError, cycle_to_millisec(cyclesError), + numTimeout, cycle_to_millisec(cyclesTimeout), + peerEndpointTest, CloseType_graceful==closeType?"graceful":CloseType_error==closeType?"error":"timeout", cycle_to_millisec(elapsedCycles)); + } + } +} + +// Legacy header sent id[2] only (but legacy clients are no longer supported since 9.6.4, i.e. they will fail during connection process) +struct ConnectHdr +{ + ConnectHdr(const SocketEndpoint &hostEp, const SocketEndpoint &remoteEp, unsigned __int64 role) { - CMPConnectThread &owner; - CThreaded threaded; - std::vector> slowClientsSocks; - CriticalSection crit; - Semaphore sem; - std::atomic stopped = true; + id[0].set(hostEp); + id[1].set(remoteEp); + hdr.size = sizeof(PacketHeader); + hdr.tag = TAG_SYS_BCAST; + hdr.flags = 0; + hdr.version = MP_PROTOCOL_VERSION; + setRole(role); + } + ConnectHdr() + { + } + SocketEndpointV4 id[2]; + PacketHeader hdr; + inline void setRole(unsigned __int64 role) + { + hdr.replytag = (mptag_t) (role >> 32); + hdr.sequence = (unsigned) (role & 0xffffffff); + } + inline unsigned __int64 getRole() const + { + return (((unsigned __int64)hdr.replytag)<<32) | ((unsigned __int64)hdr.sequence); + } +}; + + +class CMPConnectThread: public Thread +{ + class CConnectSelectHandler + { + CMPConnectThread &owner; + Owned selectHandler; + unsigned mode = SELECTMODE_READ; public: - CSlowClientProcessor(CMPConnectThread &_owner) : threaded("CSlowClientProcessor"), owner(_owner) + class CSocketHandler : public CInterfaceOf { - } - void start() + CConnectSelectHandler &selectHandler; + Owned sock; + SocketEndpoint peerEP; + StringBuffer peerHostText, peerEndpointText; + ConnectHdr hdr; + cycle_t createTime = 0; + size32_t readSoFar = 0; + CriticalSection crit; + bool closedOrHandled = false; + public: + CSocketHandler(CConnectSelectHandler &_selectHandler, ISocket *_sock, const SocketEndpoint &_peerEP) : selectHandler(_selectHandler), sock(_sock), peerEP(_peerEP) + { + createTime = get_cycles_now(); + peerEP.getHostText(peerHostText); // always used by handleAcceptedSocket + peerEndpointText.append(peerEndpointText); // only used if tracing an error + if (peerEP.port) + peerEndpointText.append(':').append(peerEP.port); + } + ISocket *querySocket() + { + return sock; + } + ConnectHdr &queryHdr() + { + return hdr; + } + cycle_t queryCreateTime() const + { + return createTime; + } + size32_t queryReadSoFar() const + { + return readSoFar; + } + const char *queryPeerHostText() const + { + return peerHostText; + } + const char *queryPeerEndpointText() const + { + return peerEndpointText; + } + bool closeIfTimedout(cycle_t now) + { + if (cycle_to_millisec(now - createTime) >= HEADER_CONFIRM_TIMEOUT) + { + // will block any pending notifySelected on this socket + CriticalBlock b(crit); + if (!closedOrHandled) + { + closedOrHandled = true; + return true; + } + } + return false; + } + // ISocketSelectNotify impl. + virtual bool notifySelected(ISocket *sock, unsigned selected) override + { + CLeavableCriticalBlock b(crit); + if (closedOrHandled) + return false; + size32_t rd = 0; + void *p = (byte *)&hdr + readSoFar; + + Owned exception; + try + { + sock->readtms(p, 0, sizeof(ConnectHdr)-readSoFar, rd, 60000); // long enough! + readSoFar += rd; + if (sizeof(ConnectHdr) == readSoFar) + { + closedOrHandled = true; + // process() will remove itself from handler, and need to avoid it doing so while in 'crit' + // since the maintenance thread could also be tyring to manipulate handlers and calling closeIfTimedout() + b.leave(); + selectHandler.process(*this); + } + } + catch (IJSOCK_Exception *e) + { + exception.setown(e); + } + if (exception) + selectHandler.close(*this, exception); + + return false; + } + }; + private: + // NB: Linked vs Owned, because methods will implicitly construct an object of this type + // which can be problematic/confusing, for example if Owned and std::list->remove is called + // with a pointer, it will auto instantiate a OWned and cause -ve leak. + std::list> handlers; + + CriticalSection handlersCS; + + std::thread maintenanceThread; + Semaphore maintenanceSem; + + void clearupSocketHandlers() { - stopped = false; - threaded.init(this, false); + std::vector> toClose; + { + cycle_t nowCycles = get_cycles_now(); + CriticalBlock b(handlersCS); + auto it = handlers.begin(); + while (true) + { + if (it == handlers.end()) + break; + CSocketHandler *socketHandler = *it; + if (socketHandler->closeIfTimedout(nowCycles)) + { + toClose.push_back(LINK(socketHandler)); + it = handlers.erase(it); + } + else + ++it; + } + } + for (auto &socketHandler: toClose) + { + try + { + Owned e = createJSocketException(JSOCKERR_timeout_expired, "Connect timeout expired", __FILE__, __LINE__); + close(*socketHandler, e); + } + catch (IException *e) + { + EXCLOG(e, "CConnectSelectHandler::maintenanceFunc"); + e->Release(); + } + } } - void stop() + public: + CConnectSelectHandler(CMPConnectThread &_owner) : owner(_owner) { - if (stopped) - return; + selectHandler.setown(createSocketSelectHandler()); + selectHandler->start(); + auto maintenanceFunc = [&] { - CriticalBlock b(crit); - stopped = true; - for (auto &sock : slowClientsSocks) - sock->close(); - slowClientsSocks.clear(); - } - - sem.signal(); - if (!threaded.join(1000*60*5)) - printf("CSlowClientProcessor::stop timed out\n"); + while (owner.running) + { + if (maintenanceSem.wait(10000)) // check every 10s + break; + clearupSocketHandlers(); + } + }; + maintenanceThread = std::thread(maintenanceFunc); + } + ~CConnectSelectHandler() + { + maintenanceSem.signal(); + maintenanceThread.join(); } - void add(ISocket *sock) // NB: takes ownership + void add(ISocket *sock, const SocketEndpoint &peerEP) { + while (true) { - CriticalBlock b(crit); - if (stopped) + unsigned numHandlers; { - sock->Release(); - return; + CriticalBlock b(handlersCS); + numHandlers = handlers.size(); } - slowClientsSocks.emplace_back(sock); + if (numHandlers < owner.maxListenHandlerSockets) + break; + DBGLOG("Too many handlers (%u), waiting for some to be processed (max limit: %u)", numHandlers, owner.maxListenHandlerSockets); + MilliSleep(1000); } - sem.signal(); + + Owned socketHandler = new CSocketHandler(*this, LINK(sock), peerEP); + + size_t numHandlers; + { + CriticalBlock b(handlersCS); + selectHandler->add(sock, mode, socketHandler); // NB: sock and handler linked by select handler + handlers.emplace_back(socketHandler); + numHandlers = handlers.size(); + } + if (0 == (numHandlers % 100)) // for info. log at each 100 boundary + DBGLOG("handlers = %u", (unsigned)numHandlers); } - // IThreaded - virtual void threadmain() override + void close(CSocketHandler &socketHandler, IJSOCK_Exception *exception) { - // The slow client processor deals with each queued slow client socket in turn, waiting the standard CONFIRM_TIMEOUT for each. - // An alternative would be to try each for shorter periods, shuffling them to the end if they still haven't been handled. - // But this is all probably OTT. The main thing is to avoid a slow client blocking the accept loop. - while (true) + if (socketHandler.queryReadSoFar()) // read something { - sem.wait(); - - Owned sock; + VStringBuffer errMsg("MP Connect Thread: invalid number of connection bytes serialized from: %s", socketHandler.queryPeerEndpointText()); + FLLOG(MCoperatorWarning, "%s", errMsg.str()); + } + int exceptionCode = exception->errorCode(); + switch (exceptionCode) + { + case JSOCKERR_timeout_expired: + trackPortProbe(socketHandler.queryCreateTime(), socketHandler.queryPeerEndpointText(), CloseType_timeout); + break; + case JSOCKERR_graceful_close: + trackPortProbe(socketHandler.queryCreateTime(), socketHandler.queryPeerEndpointText(), CloseType_graceful); + break; + default: + trackPortProbe(socketHandler.queryCreateTime(), socketHandler.queryPeerEndpointText(), CloseType_error); + break; + } - { - CriticalBlock b(crit); - if (stopped) - break; - if (slowClientsSocks.empty()) // guard, but should never happen - { - WARNLOG("slowClientsSocks list empty"); - continue; - } - sock.set(slowClientsSocks.back()); - slowClientsSocks.pop_back(); - } - owner.handleAcceptedSocket(sock.getClear(), CONFIRM_TIMEOUT, true); + Linked handler = &socketHandler; + { + CriticalBlock b(handlersCS); + selectHandler->remove(socketHandler.querySocket()); + handlers.remove(&socketHandler); } + handler->querySocket()->close(); } - } slowClientProcessor; + void process(CSocketHandler &socketHandler) + { + Linked handler = &socketHandler; + { + CriticalBlock b(handlersCS); + selectHandler->remove(socketHandler.querySocket()); + handlers.remove(&socketHandler); + } + + if (owner.threadPool) + owner.threadPool->start(handler.getClear()); + else + owner.handleAcceptedSocket(handler.getClear()); + } + }; + std::atomic running; + bool listen; + ISocket *listensock; + CMPServer *parent; + int mpSoMaxConn; + unsigned acceptThreadPoolSize = 0; + unsigned maxListenHandlerSockets = 60000; // what is a sensible default limit? + Owned threadPool; + + Owned allowListCallback; + void checkSelfDestruct(void *p,size32_t sz); + + Owned secureContextServer; + public: CMPConnectThread(CMPServer *_parent, unsigned port, bool _listen); ~CMPConnectThread() @@ -555,7 +797,6 @@ class CMPConnectThread: public Thread { if (!threadPool->joinAll(true, 1000*60*5)) printf("CMPConnectThread::stop threadPool->joinAll timed out\n"); - slowClientProcessor.stop(); } } } @@ -567,7 +808,7 @@ class CMPConnectThread: public Thread { return allowListCallback; } - bool handleAcceptedSocket(ISocket *sock, unsigned timeoutMs, bool failOnTimeout); + bool handleAcceptedSocket(CConnectSelectHandler::CSocketHandler *handler); }; class PingPacketHandler; @@ -755,7 +996,7 @@ class CMPNotifyClosedThread: public Thread } } catch (IException *e) { - FLLOG(MCoperatorWarning, e,"MP writepacket"); + FLLOG(MCoperatorWarning, e, "MP writepacket"); e->Release(); } } @@ -786,131 +1027,6 @@ class CMPNotifyClosedThread: public Thread }; -void traceSlowReadTms(const char *msg, ISocket *sock, void *dst, size32_t minSize, size32_t maxSize, size32_t &sizeRead, unsigned timeoutMs, unsigned timeoutChkIntervalMs) -{ - dbgassertex(timeoutChkIntervalMs <= timeoutMs); - StringBuffer epStr; - CCycleTimer readTmsTimer; - unsigned intervalTimeoutMs = 500; - CCycleTimer intvlTimer; - - // legacy client sends minSize, recent client sends maxSize - // if read < maxSize, keep trying for maxSize, but if its exactly minSize - // somewhat quickly (without waiting full timeout) settle for minSize ... - - if (intervalTimeoutMs > timeoutChkIntervalMs) - intervalTimeoutMs = timeoutChkIntervalMs; - - sizeRead = 0; - - unsigned firstReadTime = 0; - size32_t maxRead = maxSize; - for (;;) - { - try - { - size32_t amtRead = 0; - sock->readtms((char *)dst+sizeRead, 0, maxRead, amtRead, intervalTimeoutMs); - sizeRead += amtRead; - if (sizeRead == maxSize) - break; - maxRead -= amtRead; - } - catch (IJSOCK_Exception *e) - { - if (JSOCKERR_graceful_close == e->errorCode()) - { - e->Release(); - return; - } - else if (JSOCKERR_timeout_expired != e->errorCode()) - throw; - // interval read timed out ... - unsigned elapsedMs = readTmsTimer.elapsedMs(); - if (sizeRead == minSize) - { - if (firstReadTime == 0) - firstReadTime = elapsedMs; - else if ((elapsedMs - firstReadTime) >= 5000) // max wait if minSize sent - { - e->Release(); - break; - } - } - if (elapsedMs >= timeoutMs) - { - if (sizeRead >= minSize) - { - e->Release(); - break; - } - throw; - } - unsigned remainingMs = timeoutMs-elapsedMs; - if (remainingMs < intervalTimeoutMs) - intervalTimeoutMs = remainingMs; - if (intvlTimer.elapsedMs() >= timeoutChkIntervalMs) - { - intvlTimer.reset(); - if (0 == epStr.length()) - { - SocketEndpoint ep; - sock->getPeerEndpoint(ep); - ep.getEndpointHostText(epStr); - } - WARNLOG("%s %s, stalled for %d ms so far", msg, epStr.str(), elapsedMs); - } - e->Release(); - } - } - if (readTmsTimer.elapsedMs() >= TRACESLOW_THRESHOLD) - { - if (0 == epStr.length()) - { - SocketEndpoint ep; - sock->getPeerEndpoint(ep); - ep.getEndpointHostText(epStr); - } - WARNLOG("%s %s, took: %d ms", msg, epStr.str(), readTmsTimer.elapsedMs()); - } -} - -/* Legacy header sent id[2] only. - * To remain backward compatible (when new MP clients are connecting to old Dali), - * we send a regular empty PacketHeader as well that has the 'role' embedded within it, - * in unused fields. TAG_SYS_BCAST is used as the message tag, because it is an - * unused feature that all Dali's simply receive and delete. - */ -struct ConnectHdr -{ - ConnectHdr(const SocketEndpoint &hostEp, const SocketEndpoint &remoteEp, unsigned __int64 role) - { - id[0].set(hostEp); - id[1].set(remoteEp); - - hdr.size = sizeof(PacketHeader); - hdr.tag = TAG_SYS_BCAST; - hdr.flags = 0; - hdr.version = MP_PROTOCOL_VERSION; - setRole(role); - } - ConnectHdr() - { - } - SocketEndpointV4 id[2]; - PacketHeader hdr; - inline void setRole(unsigned __int64 role) - { - hdr.replytag = (mptag_t) (role >> 32); - hdr.sequence = (unsigned) (role & 0xffffffff); - } - inline unsigned __int64 getRole() const - { - return (((unsigned __int64)hdr.replytag)<<32) | ((unsigned __int64)hdr.sequence); - } -}; - - class CMPPacketReader; class CMPChannel: public CInterface @@ -1073,7 +1189,7 @@ protected: friend class CMPPacketReader; // if its an exception or legacy and not in allowlist the other side closes its socket after sending this msg ... size32_t amtRead = 0; - newsock->readtms(&replyBuf[totRead], 0, maxRead, amtRead, CONNECT_TIMEOUT_INTERVAL); + newsock->readtms(&replyBuf[totRead], 1, maxRead, amtRead, CONNECT_TIMEOUT_INTERVAL); totRead += amtRead; if (totRead == sizeof(size32_t)) { @@ -1512,7 +1628,7 @@ class PingPacketHandler // TAG_SYS_PING public: void handle(CMPChannel *channel,bool identifyself) { - channel->sendPingReply(CONFIRM_TIMEOUT,identifyself); + channel->sendPingReply(PING_CONFIRM_TIMEOUT, identifyself); } bool send(CMPChannel *channel,PacketHeader &hdr,CTimeMon &tm) { @@ -1700,11 +1816,12 @@ class ForwardPacketHandler // TAG_SYS_FORWARD class CMPPacketReader: public ISocketSelectNotify, public CInterface { - CMessageBuffer *activemsg; - byte * activeptr; - size32_t remaining; - CMPChannel *parent; + CMessageBuffer *activemsg = nullptr; + byte * activeptr = nullptr; + size32_t remaining = 0; + CMPChannel *parent = nullptr; CriticalSection sect; + bool gotPacketHdr = false; public: IMPLEMENT_IINTERFACE; @@ -1725,136 +1842,130 @@ class CMPPacketReader: public ISocketSelectNotify, public CInterface parent = NULL; } - bool notifySelected(ISocket *sock,unsigned selected) + bool notifySelected(ISocket *sock, unsigned selected) { if (!parent) return false; - try { - // try and mop up all data on socket - // TLS TODO: avail_read() may not return accurate amount of pending bytes - size32_t sizeavail = sock->avail_read(); - if (sizeavail==0) { - // graceful close - Linked pc; - { - CriticalBlock block(sect); - if (parent) { - pc.set(parent); // don't want channel to disappear during call - parent = NULL; - } - } - if (pc) - { -#ifdef _TRACELINKCLOSED - LOG(MCdebugInfo, "CMPPacketReader::notifySelected() about to close socket, mode = 0x%x", selected); -#endif - pc->closeSocket(false, true); - } - return false; - } - do { + try + { + while (true) // NB: breaks out if blocked (if (remaining) ..) + { + // try and mop up all data on socket parent->lastxfer = msTick(); #ifdef _FULLTRACE parent->numiter++; #endif - if (!activemsg) { // no message in progress - PacketHeader hdr; // header for active message + if (!activemsg) // no message in progress + { #ifdef _FULLTRACE parent->numiter = 1; parent->startxfer = msTick(); #endif - // assumes packet header will arrive in one go - if (sizeavailread(&hdr,sizeof(hdr),sizeof(hdr),szread,60); // I don't *really* want to block here but not much else can do - } - else - sock->read(&hdr,sizeof(hdr)); - if (hdr.version/0x100 != MP_PROTOCOL_VERSION/0x100) { + remaining = sizeof(PacketHeader); + gotPacketHdr = false; + activemsg = new CMessageBuffer(remaining); + activeptr = (byte *)activemsg->bufferBase(); + } + size32_t szRead; + if (!gotPacketHdr) + { + CCycleTimer timer; + sock->readtms(activeptr, 0, remaining, szRead, timer.remainingMs(60000)); + remaining -= szRead; + activeptr += szRead; + if (remaining) // only possible if blocked. + return false; // wait for next notification + + PacketHeader &hdr = *(PacketHeader *)activemsg->bufferBase(); + if (hdr.version/0x100 != MP_PROTOCOL_VERSION/0x100) + { // TBD IPV6 here SocketEndpoint ep; sock->getPeerEndpoint(ep); IMP_Exception *e=new CMPException(MPERR_protocol_version_mismatch,ep); throw e; } - if (sizeavail<=sizeof(hdr)) - sizeavail = sock->avail_read(); - else - sizeavail -= sizeof(hdr); -#ifdef _FULLTRACE + hdr.setMessageFields(*activemsg); + #ifdef _FULLTRACE StringBuffer ep1; StringBuffer ep2; LOG(MCdebugInfo, "MP: ReadPacket(sender=%s,target=%s,tag=%d,replytag=%d,size=%d)",hdr.sender.getEndpointHostText(ep1).str(),hdr.target.getEndpointHostText(ep2).str(),hdr.tag,hdr.replytag,hdr.size); -#endif + #endif remaining = hdr.size-sizeof(hdr); - activemsg = new CMessageBuffer(remaining); // will get from low level IO at some stage - activeptr = (byte *)activemsg->reserveTruncate(remaining); - hdr.setMessageFields(*activemsg); + activeptr = (byte *)activemsg->clear().reserveTruncate(remaining); + gotPacketHdr = true; } - - size32_t toread = sizeavail; - if (toread>remaining) - toread = remaining; - if (toread) { - sock->read(activeptr,toread); - remaining -= toread; - sizeavail -= toread; - activeptr += toread; + + if (remaining) + { + sock->readtms(activeptr, 0, remaining, szRead, WAIT_FOREVER); + remaining -= szRead; + activeptr += szRead; } - if (remaining==0) { // we have the packet so process + if (remaining) // only possible if blocked. + return false; // wait for next notification #ifdef _FULLTRACE - LOG(MCdebugInfo, "MP: ReadPacket(timetaken = %d,select iterations=%d)",msTick()-parent->startxfer,parent->numiter); + LOG(MCdebugInfo, "MP: ReadPacket(timetaken = %d,select iterations=%d)",msTick()-parent->startxfer,parent->numiter); #endif - do { - switch (activemsg->getTag()) { + do + { + switch (activemsg->getTag()) + { case TAG_SYS_MULTI: - activemsg = parent->queryServer().multipackethandler->handle(activemsg); // activemsg in/out - break; + activemsg = parent->queryServer().multipackethandler->handle(activemsg); // activemsg in/out + break; case TAG_SYS_PING: - parent->queryServer().pingpackethandler->handle(parent,false); //,activemsg); - delete activemsg; - activemsg = NULL; - break; + parent->queryServer().pingpackethandler->handle(parent,false); //,activemsg); + delete activemsg; + activemsg = NULL; + break; case TAG_SYS_PING_REPLY: - parent->queryServer().pingreplypackethandler->handle(parent); - delete activemsg; - activemsg = NULL; - break; + parent->queryServer().pingreplypackethandler->handle(parent); + delete activemsg; + activemsg = NULL; + break; case TAG_SYS_BCAST: - activemsg = parent->queryServer().broadcastpackethandler->handle(activemsg); - break; + activemsg = parent->queryServer().broadcastpackethandler->handle(activemsg); + break; case TAG_SYS_FORWARD: - activemsg = parent->queryServer().forwardpackethandler->handle(activemsg); - break; + activemsg = parent->queryServer().forwardpackethandler->handle(activemsg); + break; default: - parent->queryServer().userpackethandler->handle(activemsg); // takes ownership - activemsg = NULL; - } - } while (activemsg); + parent->queryServer().userpackethandler->handle(activemsg); // takes ownership + activemsg = NULL; + } } - if (!sizeavail) - sizeavail = sock->avail_read(); - } while (sizeavail); - return false; // ok + while (activemsg); + } } - catch (IException *e) { + catch (IException *e) + { if (e->errorCode()!=JSOCKERR_graceful_close) FLLOG(MCoperatorWarning, e,"MP(Packet Reader)"); e->Release(); + gotPacketHdr = false; } - // error here, so close socket (ignore error as may be closed already) - try { - if(parent) - parent->closeSocket(false, true); + + // here due to error or graceful close, so close socket (ignore error as may be closed already) + try + { + Linked pc; + { + CriticalBlock block(sect); + if (parent) + { + pc.set(parent); // don't want channel to disappear during call + parent = NULL; + } + } + if (pc) + pc->closeSocket(false, true); } - catch (IException *e) { + catch (IException *e) + { e->Release(); } - parent = NULL; return false; } }; @@ -1988,7 +2099,7 @@ bool CMPChannel::attachSocket(ISocket *newsock,const SocketEndpoint &_remoteep,c PROGLOG("MP: attachSocket before select add"); #endif - parent->querySelectHandler().add(channelsock,SELECTMODE_READ,reader); + parent->querySelectHandler().add(channelsock, SELECTMODE_READ, reader); #ifdef _FULLTRACE PROGLOG("MP: attachSocket after select add"); @@ -2128,7 +2239,7 @@ bool CMPChannel::sendPingReply(unsigned timeout,bool identifyself) static constexpr unsigned defaultAcceptThreadPoolSize = 100; // -------------------------------------------------------- CMPConnectThread::CMPConnectThread(CMPServer *_parent, unsigned port, bool _listen) - : Thread("MP Connection Thread"), slowClientProcessor(*this) + : Thread("MP Connection Thread") { parent = _parent; listen = _listen; @@ -2298,7 +2409,7 @@ void CMPConnectThread::startPort(unsigned short port) class CMPConnectionThread : public CInterfaceOf { CMPConnectThread &owner; - Owned sock; + Owned handler; public: CMPConnectionThread(CMPConnectThread &_owner) : owner(_owner) { @@ -2306,21 +2417,11 @@ void CMPConnectThread::startPort(unsigned short port) // IPooledThread virtual void init(void *param) override { - sock.set((ISocket *)param); + handler.setown((CConnectSelectHandler::CSocketHandler *)param); } virtual void threadmain() override { - constexpr unsigned timeoutMs = 5000; - - // detach from this pooled thread, and own locally, so that we ensure that - // the pooled thread object does not retain it until the next init() call. - Owned handledSock = sock.getClear(); - - if (owner.handleAcceptedSocket(handledSock.getLink(), timeoutMs, false)) - { - // handoff to slowClientProcessor, which will retry for standard CONFIRM_TIMEOUT period - owner.slowClientProcessor.add(handledSock.getClear()); - } + owner.handleAcceptedSocket(handler.getClear()); } virtual bool stop() override { @@ -2336,129 +2437,39 @@ void CMPConnectThread::startPort(unsigned short port) }; Owned factory = new CMPConnectThreadFactory(*this); threadPool.setown(createThreadPool("MPConnectPool", factory, false, nullptr, acceptThreadPoolSize, INFINITE)); - slowClientProcessor.start(); } Thread::start(false); } -// only returns true if !failOnTimeout and times out -bool CMPConnectThread::handleAcceptedSocket(ISocket *_sock, unsigned timeoutMs, bool failOnTimeout) +bool CMPConnectThread::handleAcceptedSocket(CConnectSelectHandler::CSocketHandler *_handler) { - SocketEndpoint peerEp; - _sock->getPeerEndpoint(peerEp); - Owned sock = _sock; + Owned handler = _handler; + ISocket *sock = handler->querySocket(); + ConnectHdr &connectHdr = handler->queryHdr(); try { -#if defined(_USE_OPENSSL) - if (parent->useTLS) - { - Owned ssock = secureContextServer->createSecureSocket(sock.getClear()); - int tlsTraceLevel = SSLogMin; - if (parent->mpTraceLevel >= MPVerboseMsgThreshold) - tlsTraceLevel = SSLogMax; - int status = ssock->secure_accept(tlsTraceLevel); - if (status < 0) - { - ssock->close(); - PROGLOG("MP Connect Thread: failed to accept secure connection"); - return false; // did not timeout - } - sock.setown(ssock.getClear()); - } -#endif // OPENSSL - -#ifdef _FULLTRACE - StringBuffer s; - SocketEndpoint ep1; - sock->getPeerEndpoint(ep1); - PROGLOG("MP: Connect Thread: socket accepted from %s",ep1.getEndpointHostText(s).str()); -#endif - - sock->set_keep_alive(true); - - size32_t rd = 0; - SocketEndpoint _remoteep; - SocketEndpoint hostep; - ConnectHdr connectHdr; - bool legacyClient = false; - - // NB: min size is ConnectHdr.id for legacy clients, can thus distinguish old from new - try - { - traceSlowReadTms("MP: initial accept packet from", sock, &connectHdr, sizeof(connectHdr.id), sizeof(connectHdr), rd, timeoutMs, CONFIRM_TIMEOUT_INTERVAL); - } - catch (IJSOCK_Exception *e) - { - if (JSOCKERR_timeout_expired != e->errorCode()) - throw; - if (!failOnTimeout) - return true; // timedout (socket kept open) - } - if (0 == rd) - { - if (parent->mpTraceLevel >= MPVerboseMsgThreshold) - { - // cannot get peer addresss as socket state is now ss_shutdown (unless we want to allow this in getPeerEndpoint()) - PROGLOG("MP Connect Thread: connect with no msg received, assumed port monitor check"); - } - sock->close(); - return false; // did not timeout - } - else - { - if (rd == sizeof(connectHdr.id)) // legacy client - { - legacyClient = true; - connectHdr.hdr.size = sizeof(PacketHeader); - connectHdr.hdr.tag = TAG_SYS_BCAST; - connectHdr.hdr.flags = 0; - connectHdr.hdr.version = MP_PROTOCOL_VERSION; - connectHdr.setRole(0); // unknown - } - else if (rd < sizeof(connectHdr.id) || rd > sizeof(connectHdr)) - { - // not sure how to get here as this is not one of the possible outcomes of above: rd == 0 or rd == sizeof(id) or an exception - StringBuffer errMsg("MP Connect Thread: invalid number of connection bytes serialized from "); - peerEp.getEndpointHostText(errMsg); - FLLOG(MCoperatorWarning, "%s", errMsg.str()); - sock->close(); - return false; // did not timeout - } - } - if (allowListCallback) { - StringBuffer ipStr; - peerEp.getHostText(ipStr); StringBuffer responseText; // filled if denied, NB: if amount sent is > sizeof(ConnectHdr) we can differentiate exception from success - if (!allowListCallback->isAllowListed(ipStr, connectHdr.getRole(), &responseText)) + if (!allowListCallback->isAllowListed(handler->queryPeerHostText(), connectHdr.getRole(), &responseText)) { Owned e = makeStringException(-1, responseText); OWARNLOG(e, nullptr); - if (legacyClient) - { - /* NB: legacy client can't handle exception response - * Acknowledge legacy connection, then close socket - * The effect will be the client sees an MPERR_link_closed - */ - size32_t reply = sizeof(connectHdr.id); - sock->write(&reply, sizeof(reply)); - } - else - { - MemoryBuffer mb; - DelayedSizeMarker marker(mb); - serializeException(e, mb); - marker.write(); - sock->write(mb.toByteArray(), mb.length()); - } + // NB: from 9.6 legacy clients are no longer supported (legacy clients in this context are older than 7.4.2) + MemoryBuffer mb; + DelayedSizeMarker marker(mb); + serializeException(e, mb); + marker.write(); + sock->write(mb.toByteArray(), mb.length()); sock->close(); return false; // did not timeout } } + SocketEndpoint _remoteep; + SocketEndpoint hostep; connectHdr.id[0].get(_remoteep); connectHdr.id[1].get(hostep); @@ -2475,19 +2486,17 @@ bool CMPConnectThread::handleAcceptedSocket(ISocket *_sock, unsigned timeoutMs, if (memcmp(connectHdr.id, zeroTest, sizeof(connectHdr.id))) { // JCSMORE, I think _remoteep really must/should match a IP of this local host - errMsg.append("MP Connect Thread: invalid remote and/or host ep serialized from "); - peerEp.getEndpointHostText(errMsg); + errMsg.appendf("MP Connect Thread: invalid remote and/or host ep serialized from %s", handler->queryPeerEndpointText()); FLLOG(MCoperatorWarning, "%s", errMsg.str()); } else if (parent->mpTraceLevel >= MPVerboseMsgThreshold) { // all zeros msg received - errMsg.append("MP Connect Thread: connect with empty msg received, assumed port monitor check from "); - peerEp.getEndpointHostText(errMsg); + errMsg.appendf("MP Connect Thread: connect with empty msg received, assumed port monitor check from %s", handler->queryPeerEndpointText()); PROGLOG("%s", errMsg.str()); } sock->close(); - return false; // did not timeout + return false; } #ifdef _FULLTRACE StringBuffer tmp1; @@ -2498,7 +2507,8 @@ bool CMPConnectThread::handleAcceptedSocket(ISocket *_sock, unsigned timeoutMs, #endif checkSelfDestruct(&connectHdr.id[0],sizeof(connectHdr.id)); Owned channel = parent->lookup(_remoteep); - if (!channel->attachSocket(sock.getClear(),_remoteep,hostep,false,&rd,addrval)) + size32_t rd = sizeof(connectHdr); + if (!channel->attachSocket(LINK(sock),_remoteep,hostep,false,&rd,addrval)) { #ifdef _FULLTRACE PROGLOG("MP Connect Thread: lookup failed"); @@ -2522,7 +2532,7 @@ bool CMPConnectThread::handleAcceptedSocket(ISocket *_sock, unsigned timeoutMs, sock->close(); e->Release(); } - return false; // did not timeout + return false; } int CMPConnectThread::run() @@ -2531,13 +2541,15 @@ int CMPConnectThread::run() LOG(MCdebugInfo, "MP: Connect Thread Starting - accept loop"); #endif Owned exception; + + CConnectSelectHandler connectSelectHandler(*this); while (running) { Owned sock; - SocketEndpoint peerEp; + SocketEndpoint peerEP; try { - sock.setown(listensock->accept(true, &peerEp)); + sock.setown(listensock->accept(true, &peerEP)); } catch (IException *e) { @@ -2545,14 +2557,38 @@ int CMPConnectThread::run() } if (sock) { - if (threadPool) +#if defined(_USE_OPENSSL) + if (parent->useTLS) { - // if enabled, handle initial connection protocol on a separate thread - // if any block for more than a short period (5 seconds), then they are handed off to slowClientProcessor - threadPool->start(sock); + Owned ssock = secureContextServer->createSecureSocket(sock.getClear()); + int tlsTraceLevel = SSLogMin; + if (parent->mpTraceLevel >= MPVerboseMsgThreshold) + tlsTraceLevel = SSLogMax; + int status = ssock->secure_accept(tlsTraceLevel); + if (status < 0) + { + ssock->close(); + PROGLOG("MP Connect Thread: failed to accept secure connection"); + continue; + } + sock.setown(ssock.getClear()); } - else - handleAcceptedSocket(sock.getClear(), CONFIRM_TIMEOUT, true); +#endif // OPENSSL + +#ifdef _FULLTRACE + StringBuffer s; + SocketEndpoint ep1; + sock->getPeerEndpoint(ep1); + PROGLOG("MP: Connect Thread: socket accepted from %s",ep1.getEndpointHostText(s).str()); +#endif + sock->set_keep_alive(true); + + // NB: creates a CSocketHandler that is added to the select handler. + // it will manage the handling of the incoming ConnectHdr header only. + // After that, the socket will be removed from the connectSelectHamndler, + // a CMPChannel will be estalbished, and the socket will be added to the MP CMPPacketReader select handler. + // See handleAcceptedSocket. + connectSelectHandler.add(sock, peerEP); } else { diff --git a/system/security/LdapSecurity/ldapconnection.cpp b/system/security/LdapSecurity/ldapconnection.cpp index aaf426e8d67..14fe3b7d9d5 100644 --- a/system/security/LdapSecurity/ldapconnection.cpp +++ b/system/security/LdapSecurity/ldapconnection.cpp @@ -3226,20 +3226,23 @@ class CLdapClient : implements ILdapClient, public CInterface return true; } - virtual bool changePasswordSSL(const char* username, const char* newPassword) + virtual bool changePasswordSSL(const char* username, const char* newPassword, LDAP* ld) { Owned lconn; - try - { - lconn.setown(m_connections->getSSLConnection()); - } - catch(IException*) + if (ld == nullptr) { - throw MakeStringException(-1, "Failed to set user %s's password because of not being able to create an SSL connection to the ldap server. To set an Active Directory user's password from Linux, you need to enable SSL on the Active Directory ldap server", username); + try + { + lconn.setown(m_connections->getSSLConnection()); + } + catch (IException *e) + { + e->Release(); + throw MakeStringException(-1, "Failed to set user %s's password because of not being able to create an SSL connection to the ldap server. To set an Active Directory user's password from Linux, you need to enable SSL on the Active Directory ldap server", username); + } + ld = lconn.get()->getLd(); } - LDAP* ld = lconn.get()->getLd(); - char *attribute, **values = NULL; LDAPMessage *message; @@ -3347,7 +3350,7 @@ class CLdapClient : implements ILdapClient, public CInterface return false; } - virtual bool updateUserPassword(ISecUser& user, const char* newPassword, const char* currPassword) + virtual bool updateUserPassword(ISecUser& user, const char* newPassword, const char* currPassword, LDAP* ld) { const char* username = user.getName(); if(!username || !*username) @@ -3372,10 +3375,10 @@ class CLdapClient : implements ILdapClient, public CInterface throw MakeStringException(-1, "Password not changed, invalid credentials"); } - return updateUserPassword(username, newPassword); + return updateUserPassword(username, newPassword, ld); } - virtual bool updateUserPassword(const char* username, const char* newPassword) + virtual bool updateUserPassword(const char* username, const char* newPassword, LDAP* ld) { if(!username || !*username) { @@ -3472,7 +3475,7 @@ class CLdapClient : implements ILdapClient, public CInterface } DBGLOG("Trying changePasswordSSL to change password over regular SSL connection."); #endif - changePasswordSSL(username, newPassword); + changePasswordSSL(username, newPassword, ld); } else { @@ -3485,7 +3488,7 @@ class CLdapClient : implements ILdapClient, public CInterface TIMEVAL timeOut = {m_ldapconfig->getLdapTimeout(),0}; Owned lconn = m_connections->getConnection(); - LDAP* ld = lconn.get()->getLd(); + ld = lconn.get()->getLd(); char *attrs[] = {LDAP_NO_ATTRS, NULL}; CLDAPMessage searchResult; @@ -6032,7 +6035,7 @@ class CLdapClient : implements ILdapClient, public CInterface // set the password. Owned tmpuser = new CLdapSecUser(user->getName(), ""); - if (!updateUserPassword(*tmpuser, user->credentials().getPassword(), nullptr)) + if (!updateUserPassword(*tmpuser, user->credentials().getPassword(), nullptr, ld)) { DBGLOG("Error updating password for %s",username); throw MakeStringException(-1, "Error updating password for %s",username); @@ -6069,6 +6072,7 @@ class CLdapClient : implements ILdapClient, public CInterface virtual bool addUser(ISecUser& user) { + LdapServerType serverType = m_ldapconfig->getServerType(); const char* username = user.getName(); if(username == NULL || *username == '\0') { @@ -6111,7 +6115,7 @@ class CLdapClient : implements ILdapClient, public CInterface const char* employeeNumber = user.getEmployeeNumber(); StringBuffer dn; - if(m_ldapconfig->getServerType() == ACTIVE_DIRECTORY) + if(serverType == ACTIVE_DIRECTORY) { dn.append("cn=").append(fullname).append(","); } @@ -6123,7 +6127,7 @@ class CLdapClient : implements ILdapClient, public CInterface char* oc_name; char* act_fieldname; - if(m_ldapconfig->getServerType() == ACTIVE_DIRECTORY) + if(serverType == ACTIVE_DIRECTORY) { oc_name = "User"; act_fieldname = "sAMAccountName"; @@ -6223,7 +6227,7 @@ class CLdapClient : implements ILdapClient, public CInterface attrs[ind++] = &sn_attr; attrs[ind++] = &actname_attr; - if(m_ldapconfig->getServerType() == ACTIVE_DIRECTORY) + if(serverType == ACTIVE_DIRECTORY) { attrs[ind++] = &username_attr; attrs[ind++] = &dispname_attr; @@ -6239,7 +6243,19 @@ class CLdapClient : implements ILdapClient, public CInterface attrs[ind] = NULL; - Owned lconn = m_connections->getConnection(); + // + // If the server type is ACTIVE_DIRECTORY, an SSL connection will be needed later to + // set the new user password, otherwise a non SSL connection is used. + Owned lconn; + if(serverType == ACTIVE_DIRECTORY) + { + lconn.setown(m_connections->getSSLConnection()); + } + else + { + lconn.setown(m_connections->getConnection()); + } + LDAP* ld = lconn.get()->getLd(); int rc = ldap_add_ext_s(ld, (char*)dn.str(), attrs, NULL, NULL); if ( rc != LDAP_SUCCESS ) @@ -6256,7 +6272,7 @@ class CLdapClient : implements ILdapClient, public CInterface } } - if(m_ldapconfig->getServerType() == ACTIVE_DIRECTORY) + if(serverType == ACTIVE_DIRECTORY) { try { diff --git a/system/security/LdapSecurity/ldapconnection.hpp b/system/security/LdapSecurity/ldapconnection.hpp index 51d148e763d..ec198d412d8 100644 --- a/system/security/LdapSecurity/ldapconnection.hpp +++ b/system/security/LdapSecurity/ldapconnection.hpp @@ -287,9 +287,9 @@ interface ILdapClient : extends IInterface virtual void setResourceBasedn(const char* rbasedn, SecResourceType rtype = RT_DEFAULT) = 0; virtual ILdapConfig* getLdapConfig() = 0; virtual bool userInGroup(const char* userdn, const char* groupdn) = 0; - virtual bool updateUserPassword(ISecUser& user, const char* newPassword, const char* currPassword = 0) = 0; + virtual bool updateUserPassword(ISecUser& user, const char* newPassword, const char* currPassword = 0, LDAP* ld = nullptr) = 0; virtual bool updateUser(const char* type, ISecUser& user) = 0; - virtual bool updateUserPassword(const char* username, const char* newPassword) = 0; + virtual bool updateUserPassword(const char* username, const char* newPassword, LDAP* ld = nullptr) = 0; virtual bool getResources(SecResourceType rtype, const char * basedn, const char* prefix, const char* searchstr, IArrayOf& resources) = 0; virtual IPropertyTreeIterator* getResourceIterator(SecResourceType rtype, const char * basedn, const char* prefix, const char* resourceName, unsigned extraNameFilter) = 0; diff --git a/system/security/securesocket/securesocket.cpp b/system/security/securesocket/securesocket.cpp index 616203b62fb..c9b2f675855 100644 --- a/system/security/securesocket/securesocket.cpp +++ b/system/security/securesocket/securesocket.cpp @@ -137,15 +137,8 @@ class CStringSet : public CInterface class CSecureSocket : implements ISecureSocket, public CInterface { private: - struct ScopedNonBlockingMode - { - CSecureSocket *socket = nullptr; - bool prevMode = false; - void init(CSecureSocket *_socket) { socket = _socket; prevMode = socket->set_nonblock(true); } - ~ScopedNonBlockingMode() { if (socket) socket->set_nonblock(prevMode); } - }; - SSL* m_ssl; + StringBuffer epStr; Linked contextCallback; Owned m_socket; bool nonBlocking; @@ -166,11 +159,12 @@ class CSecureSocket : implements ISecureSocket, public CInterface private: StringBuffer& get_cn(X509* cert, StringBuffer& cn); bool verify_cert(X509* cert); + void handleError(int ssl_err, bool writing, bool wait, unsigned timeoutMs, const char *opStr); public: IMPLEMENT_IINTERFACE; - CSecureSocket(ISocket* sock, int sockfd, ISecureSocketContextCallback * callback, bool verify = false, bool addres_match = false, CStringSet* m_peers = NULL, int loglevel=SSLogNormal, const char *fqdn = nullptr); + CSecureSocket(ISocket* sock, ISecureSocketContextCallback * callback, bool verify = false, bool addres_match = false, CStringSet* m_peers = NULL, int loglevel=SSLogNormal, const char *fqdn = nullptr); ~CSecureSocket(); virtual int secure_accept(int logLevel); @@ -178,8 +172,8 @@ class CSecureSocket : implements ISecureSocket, public CInterface virtual int logPollError(unsigned revents, const char *rwstr); virtual int wait_read(unsigned timeoutms); - virtual void read(void* buf, size32_t min_size, size32_t max_size, size32_t &size_read,unsigned timeoutsecs); - virtual void readtms(void* buf, size32_t min_size, size32_t max_size, size32_t &size_read, unsigned timeoutms); + virtual void read(void* buf, size32_t min_size, size32_t max_size, size32_t &size_read,unsigned timeoutsecs, bool suppresGCIfMinSize=true); + virtual void readtms(void* buf, size32_t min_size, size32_t max_size, size32_t &size_read, unsigned timeoutms, bool suppresGCIfMinSize=true); virtual size32_t write(void const* buf, size32_t size); virtual size32_t writetms(void const* buf, size32_t minSize, size32_t size, unsigned timeoutms=WAIT_FOREVER); @@ -499,12 +493,14 @@ Semaphore CSecureSocket::receiveblocksem(2); /************************************************************************** * CSecureSocket -- secure socket layer implementation using openssl * **************************************************************************/ -CSecureSocket::CSecureSocket(ISocket* sock, int sockfd, ISecureSocketContextCallback * callback, bool verify, bool address_match, CStringSet* peers, int loglevel, const char *fqdn) +CSecureSocket::CSecureSocket(ISocket* sock, ISecureSocketContextCallback * callback, bool verify, bool address_match, CStringSet* peers, int loglevel, const char *fqdn) : contextCallback(callback) { - if (sock) - sockfd = sock->OShandle(); m_socket.setown(sock); + int sockfd = sock->OShandle(); + SocketEndpoint ep; + sock->getPeerEndpoint(ep); + ep.getEndpointHostText(epStr); contextVersion = callback->getVersion(); m_ssl = callback->createActiveSSL(); @@ -682,82 +678,156 @@ int CSecureSocket::secure_accept(int logLevel) { checkForUpdatedContext(); int err; - err = SSL_accept(m_ssl); - if(err == 0) - { - int ret = SSL_get_error(m_ssl, err); - // if err == 0 && ret == SSL_ERROR_SYSCALL - // then client closed connection gracefully before ssl neg - // which can happen with port scan / VIP ... - // NOTE: ret could also be SSL_ERROR_ZERO_RETURN if client closed - // gracefully after ssl neg initiated ... - if ( (logLevel > SSLogNormal) || (ret != SSL_ERROR_SYSCALL) ) + while (true) + { + err = SSL_accept(m_ssl); + if (err > 0) { - char errbuf[512]; - ERR_error_string_n(ERR_get_error(), errbuf, 512); - DBGLOG("SSL_accept returned 0, error - %s", errbuf); + if (logLevel > SSLogNormal) + DBGLOG("SSL accept ok, using %s", SSL_get_cipher(m_ssl)); + + if (m_verify) + { + bool verified = false; + // Get client's certificate (note: beware of dynamic allocation) - opt + X509* client_cert = SSL_get_peer_certificate (m_ssl); + if (client_cert != NULL) + { + // We could do all sorts of certificate verification stuff here before + // deallocating the certificate. + verified = verify_cert(client_cert); + X509_free (client_cert); + } + + if (!verified) + throw MakeStringException(-1, "certificate verification failed"); + } + + m_isSecure = true; + return 0; } - if (ret == SSL_ERROR_SYSCALL) - return PORT_CHECK_SSL_ACCEPT_ERROR; - return -1; - } - else if(err < 0) - { - int ret = SSL_get_error(m_ssl, err); - unsigned long errnum = ERR_get_error(); - // Since err < 0 we call ERR_get_error() for additional info - // if ret == SSL_ERROR_SYSCALL and ERR_get_error() == 0 then - // its most likely a port scan / load balancer check so do not log - // with SSL 1.1.1e and 3.0 if ret == SSL_ERROR_SSL and ERR_get_error reason is EOF - // its also most likely a port scan / load balancer check so do not log - int srtn = err; - if ( (ret == SSL_ERROR_SYSCALL) && (errnum == 0) ) - srtn = PORT_CHECK_SSL_ACCEPT_ERROR; - // if ctx option SSL_OP_IGNORE_UNEXPECTED_EOF is set then will get SSL_ERROR_ZERO_RETURN ... - if ( (ret == SSL_ERROR_ZERO_RETURN) && (errnum == 0) ) - srtn = PORT_CHECK_SSL_ACCEPT_ERROR; - // otherwise will get SSL_ERROR_SSL and unexpected eof ... + else + { + int ret = SSL_get_error(m_ssl, err); + if (err == 0) + { + // if err == 0 && ret == SSL_ERROR_SYSCALL + // then client closed connection gracefully before ssl neg + // which can happen with port scan / VIP ... + // NOTE: ret could also be SSL_ERROR_ZERO_RETURN if client closed + // gracefully after ssl neg initiated ... + if ( (logLevel > SSLogNormal) || (ret != SSL_ERROR_SYSCALL) ) + { + char errbuf[512]; + ERR_error_string_n(ERR_get_error(), errbuf, 512); + DBGLOG("SSL_accept returned 0, error - %s", errbuf); + } + if (ret == SSL_ERROR_SYSCALL) + return PORT_CHECK_SSL_ACCEPT_ERROR; + return -1; + } + + unsigned long errnum = ERR_get_error(); + // Since err < 0 we call ERR_get_error() for additional info + // if ret == SSL_ERROR_SYSCALL and ERR_get_error() == 0 then + // its most likely a port scan / load balancer check so do not log + // with SSL 1.1.1e and 3.0 if ret == SSL_ERROR_SSL and ERR_get_error reason is EOF + // its also most likely a port scan / load balancer check so do not log + int srtn = err; + if ( (ret == SSL_ERROR_SYSCALL) && (errnum == 0) ) + srtn = PORT_CHECK_SSL_ACCEPT_ERROR; + // if ctx option SSL_OP_IGNORE_UNEXPECTED_EOF is set then will get SSL_ERROR_ZERO_RETURN ... + if ( (ret == SSL_ERROR_ZERO_RETURN) && (errnum == 0) ) + srtn = PORT_CHECK_SSL_ACCEPT_ERROR; + // otherwise will get SSL_ERROR_SSL and unexpected eof ... #if defined(SSL_R_UNEXPECTED_EOF_WHILE_READING) - if ( (ret == SSL_ERROR_SSL) && (ERR_GET_REASON(errnum) == SSL_R_UNEXPECTED_EOF_WHILE_READING) ) - srtn = PORT_CHECK_SSL_ACCEPT_ERROR; + if ( (ret == SSL_ERROR_SSL) && (ERR_GET_REASON(errnum) == SSL_R_UNEXPECTED_EOF_WHILE_READING) ) + srtn = PORT_CHECK_SSL_ACCEPT_ERROR; #endif - if ((logLevel <= SSLogNormal) && (srtn == PORT_CHECK_SSL_ACCEPT_ERROR)) - return srtn; - char errbuf[512]; - ERR_error_string_n(errnum, errbuf, 512); - errbuf[511] = '\0'; - DBGLOG("SSL_accept returned %d, SSL_get_error=%d, error - %s", err, ret, errbuf); - if(strstr(errbuf, "error:1408F455:") != NULL) - { - DBGLOG("Unrecoverable SSL library error."); - _exit(0); + // JCSMORE this should really handle accept_cancel_pending + if (PORT_CHECK_SSL_ACCEPT_ERROR != srtn) + handleError(ret, false, true, WAIT_FOREVER, "SSL_accept"); + else + { + if ((logLevel <= SSLogNormal) && (srtn == PORT_CHECK_SSL_ACCEPT_ERROR)) + return srtn; + char errbuf[512]; + ERR_error_string_n(errnum, errbuf, 512); + errbuf[511] = '\0'; + DBGLOG("SSL_accept returned %d, SSL_get_error=%d, error - %s", err, ret, errbuf); + if(strstr(errbuf, "error:1408F455:") != NULL) + { + DBGLOG("Unrecoverable SSL library error."); + _exit(0); + } + return srtn; + } } - return srtn; } +} - if (logLevel > SSLogNormal) - DBGLOG("SSL accept ok, using %s", SSL_get_cipher(m_ssl)); - - if(m_verify) +void CSecureSocket::handleError(int ssl_err, bool writing, bool wait, unsigned timeoutMs, const char *opStr) +{ + // if !wait, then we only perform ssl_err checking, we do not wait_read/wait_write or timeout + int rc = 0; + switch (ssl_err) { - bool verified = false; - // Get client's certificate (note: beware of dynamic allocation) - opt - X509* client_cert = SSL_get_peer_certificate (m_ssl); - if (client_cert != NULL) + case SSL_ERROR_ZERO_RETURN: { - // We could do all sorts of certificate verification stuff here before - // deallocating the certificate. - verified = verify_cert(client_cert); - X509_free (client_cert); + THROWJSOCKEXCEPTION(JSOCKERR_graceful_close); + } + case SSL_ERROR_WANT_READ: // NB: SSL_write can provoke SSL_ERROR_WANT_READ + { + if (wait) + rc = wait_read(timeoutMs); + break; + } + case SSL_ERROR_WANT_WRITE: // NB: SSL_read can provoke SSL_ERROR_WANT_WRITE + { + if (wait) + rc = wait_write(timeoutMs); + break; + } + case SSL_ERROR_SYSCALL: + { + int sockErr = SOCKETERRNO(); + if (sockErr == EAGAIN || sockErr == EWOULDBLOCK) + { + if (wait) + { + if (writing) + rc = wait_write(timeoutMs); + else + rc = wait_read(timeoutMs); + } + break; + } + // fall through to default error handling below + } + default: + { + char errbuf[512]; + ERR_error_string_n(ssl_err, errbuf, 512); + ERR_clear_error(); + VStringBuffer errmsg("%s error %d - %s", opStr, ssl_err, errbuf); + if (m_loglevel >= SSLogMax) + DBGLOG("Warning: %s", errmsg.str()); + THROWJSOCKEXCEPTION_MSG(ssl_err, errmsg); } - - if(!verified) - throw MakeStringException(-1, "certificate verification failed"); - } - - m_isSecure = true; - return 0; + if (wait && rc <= 0) + { + int code = SOCKETERRNO(); + VStringBuffer errorMsg("%s: %s ", opStr, (SSL_ERROR_WANT_WRITE == ssl_err) ? "wait_write" : "wait_read"); + if (rc == 0) + { + code = JSOCKERR_timeout_expired; + errorMsg.append("timeout expired"); + } + else + errorMsg.append("error"); + THROWJSOCKEXCEPTION_MSG(code, errorMsg.str()); + } } int CSecureSocket::secure_connect(int logLevel) @@ -768,14 +838,16 @@ int CSecureSocket::secure_connect(int logLevel) SSL_set_tlsext_host_name(m_ssl, m_fqdn.str()); } - int err = SSL_connect (m_ssl); - if(err <= 0) + unsigned timeoutMs = 60*1000; // more than enough, used to be infinite + CCycleTimer timer; + while (true) { - int ret = SSL_get_error(m_ssl, err); - char errbuf[512]; - ERR_error_string_n(ERR_get_error(), errbuf, 512); - DBGLOG("SSL_connect error - %s, SSL_get_error=%d, error - %d", errbuf,ret, err); - throw MakeStringException(-1, "SSL_connect failed: %s", errbuf); + int rc = SSL_connect(m_ssl); + if (rc > 0) + break; + int ssl_err = SSL_get_error(m_ssl, rc); + unsigned remainingMs = timer.remainingMs(timeoutMs); + handleError(ssl_err, true, true, remainingMs, "SSL_connect"); } if (logLevel > SSLogNormal) @@ -829,81 +901,53 @@ int CSecureSocket::wait_read(unsigned timeoutms) return m_socket->wait_read(timeoutms); } -void CSecureSocket::readtms(void* buf, size32_t min_size, size32_t max_size, size32_t &sizeRead, unsigned timeoutMs) +void CSecureSocket::readtms(void* buf, size32_t min_size, size32_t max_size, size32_t &sizeRead, unsigned timeoutMs, bool suppresGCIfMinSize) { + // Adheres to same semantics as CSocket::readtms + // NB: when handling poll notifications, we must read until SSL_read indicates would block + // because we may not get another poll notification because SSL internally has read everything. sizeRead = 0; CCycleTimer timer; - - // for semantics to work with a timeout, have to be non-blocking when reading SSL - // because wait_read can't guarantee that there are bytes ready to read, only that - // there are bytes pending on the underlying socket. - // We put in non-blocking mode, so that if after wait_read says there's something, - // SSL_read won't block and will respond with a SSL_ERROR_WANT_READ/SSL_ERROR_WANT_WRITE - // if not ready. - ScopedNonBlockingMode scopedNonBlockingMode; - if (WAIT_FOREVER != timeoutMs) - scopedNonBlockingMode.init(this); - - int ssl_err = SSL_ERROR_WANT_READ; // initially call will be wait_read while (true) { - int rc; - unsigned remainingMs = timer.remainingMs(timeoutMs); - if (ssl_err == SSL_ERROR_WANT_READ) - rc = wait_read(remainingMs); - else // SSL_ERROR_WANT_WRITE - rc = wait_write(remainingMs); - if (rc < 0) - THROWJSOCKEXCEPTION_MSG(SOCKETERRNO(), "wait_read error"); - if (rc == 0) - THROWJSOCKEXCEPTION_MSG(JSOCKERR_timeout_expired, "timeout expired"); - ERR_clear_error(); - rc = SSL_read(m_ssl, (char*)buf + sizeRead, max_size - sizeRead); - + int rc = SSL_read(m_ssl, (char*)buf + sizeRead, max_size - sizeRead); + unsigned remainingMs = timer.remainingMs(timeoutMs); if (rc > 0) { sizeRead += rc; - if (sizeRead >= min_size) + if (sizeRead == max_size) break; + if (0 == remainingMs) + { + if (sizeRead >= min_size) + break; + THROWJSOCKEXCEPTION_MSG(JSOCKERR_timeout_expired, "timeout expired"); + } + // loop around to read more, or detect blocked (and exit if sizeRead >= min_size) } else if (0 == rc) { - if (sizeRead >= min_size) - break; // suppress graceful close exception if have already read minimum + if (suppresGCIfMinSize && (sizeRead >= min_size)) + break; THROWJSOCKEXCEPTION(JSOCKERR_graceful_close); } else { - ssl_err = SSL_get_error(m_ssl, rc); - // NB: if timeout != WAIT_FOREVER, nonBlocking should always be true here - if (nonBlocking && (ssl_err == SSL_ERROR_WANT_READ || ssl_err == SSL_ERROR_WANT_WRITE)) // NB: SSL_read can cause SSL_ERROR_WANT_WRITE - { - // NB: we must be below min_size if here (otherwise would have exited in (rc > 0) block above) - - // To maintain consistent semantics with jsocket, we continue waiting even in the min_size = 0 case. - // NB: jsocket::readtms always blocks (wait_read) initially, meaning in effect min_size is always treated as >0 - } - else - { - char errbuf[512]; - ERR_error_string_n(ssl_err, errbuf, 512); - ERR_clear_error(); - VStringBuffer errmsg("SSL_read error %d - %s", ssl_err, errbuf); - if (m_loglevel >= SSLogMax) - DBGLOG("Warning: %s", errmsg.str()); - THROWJSOCKEXCEPTION_MSG(ssl_err, errmsg); - } - // here only if nonBlocking && WANT_READ or WANT_WRITE - // since we do not have size_min yet, loop around and wait for more. + // NB: if blocked, return if sizeRead >= min_size + int ssl_err = SSL_get_error(m_ssl, rc); + bool wait = sizeRead < min_size; // if >= min_size, then handleError will validate errors only + handleError(ssl_err, false, wait, remainingMs, "SSL_read"); + if (sizeRead >= min_size) + break; } } } -void CSecureSocket::read(void* buf, size32_t min_size, size32_t max_size, size32_t &size_read,unsigned timeoutsecs) +void CSecureSocket::read(void* buf, size32_t min_size, size32_t max_size, size32_t &size_read, unsigned timeoutsecs, bool suppresGCIfMinSize) { unsigned timeoutMs = (timeoutsecs==WAIT_FOREVER) ? WAIT_FOREVER : (timeoutsecs * 1000); - readtms(buf, min_size, max_size, size_read, timeoutMs); + readtms(buf, min_size, max_size, size_read, timeoutMs, suppresGCIfMinSize); } size32_t CSecureSocket::writetms(void const* buf, size32_t minSize, size32_t size, unsigned timeoutMs) @@ -912,11 +956,6 @@ size32_t CSecureSocket::writetms(void const* buf, size32_t minSize, size32_t siz return 0; CCycleTimer timer; - - ScopedNonBlockingMode scopedNonBlockingMode; - if (WAIT_FOREVER != timeoutMs) - scopedNonBlockingMode.init(this); - while (true) { int rc = SSL_write(m_ssl, buf, size); @@ -927,32 +966,8 @@ size32_t CSecureSocket::writetms(void const* buf, size32_t minSize, size32_t siz return rc; } int ssl_err = SSL_get_error(m_ssl, rc); - if (nonBlocking && (ssl_err == SSL_ERROR_WANT_READ || ssl_err == SSL_ERROR_WANT_WRITE)) // NB: SSL_write can cause SSL_ERROR_WANT_READ - { - unsigned remainingMs = timer.remainingMs(timeoutMs); - if (ssl_err == SSL_ERROR_WANT_READ) - rc = wait_read(remainingMs); - else // SSL_ERROR_WANT_WRITE - rc = wait_write(remainingMs); - if (rc < 0) - { - const char *msg = (ssl_err == SSL_ERROR_WANT_READ) ? "wait_read error" : "wait_write error"; - THROWJSOCKEXCEPTION_MSG(SOCKETERRNO(), msg); - } - if (rc == 0) - THROWJSOCKEXCEPTION_MSG(JSOCKERR_timeout_expired, "timeout expired"); - } - else - { - char errbuf[512]; - ERR_error_string_n(ssl_err, errbuf, 512); - ERR_clear_error(); - VStringBuffer errmsg("SSL_write error %d - %s", ssl_err, errbuf); - if (ssl_err == SSL_ERROR_ZERO_RETURN) - THROWJSOCKEXCEPTION(JSOCKERR_graceful_close); - else - THROWJSOCKEXCEPTION_MSG(JSOCKERR_broken_pipe, errmsg); - } + unsigned remainingMs = timer.remainingMs(timeoutMs); + handleError(ssl_err, true, true, remainingMs, "SSL_write"); } throwUnexpected(); // should never get here } @@ -1514,12 +1529,13 @@ class CSecureSocketContext : implements ISecureSocketContext, implements ISecure //interface ISecureSocketContext ISecureSocket* createSecureSocket(ISocket* sock, int loglevel, const char *fqdn) { - return new CSecureSocket(sock, 0, this, m_verify, m_address_match, m_peers, loglevel, fqdn); + return new CSecureSocket(sock, this, m_verify, m_address_match, m_peers, loglevel, fqdn); } ISecureSocket* createSecureSocket(int sockfd, int loglevel, const char *fqdn) { - return new CSecureSocket(nullptr, sockfd, this, m_verify, m_address_match, m_peers, loglevel, fqdn); + Owned sock = ISocket::attach(sockfd); + return new CSecureSocket(sock.getClear(), this, m_verify, m_address_match, m_peers, loglevel, fqdn); } //interface ISecureSocketContextCallback diff --git a/testing/unittests/jlibtests.cpp b/testing/unittests/jlibtests.cpp index 49e68b06df0..816c50a8a8b 100644 --- a/testing/unittests/jlibtests.cpp +++ b/testing/unittests/jlibtests.cpp @@ -4780,4 +4780,233 @@ class JLibStringTest : public CppUnit::TestFixture CPPUNIT_TEST_SUITE_REGISTRATION( JLibStringTest ); CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( JLibStringTest, "JLibStringTest" ); +// ======================================== + +class CAddrThreadArgs : public CInterface, implements IInterface +{ +public: + IMPLEMENT_IINTERFACE; + StringAttr name; + unsigned timeoutms; + bool logIt = true; + + CAddrThreadArgs(const char *_name, unsigned _timeoutms) : name(_name), timeoutms(_timeoutms) + { + } + + CAddrThreadArgs(const char *_name, unsigned _timeoutms, bool _logIt) : name(_name), timeoutms(_timeoutms), logIt(_logIt) + { + } +}; + +class CAddrPoolFactory : public CInterface, public IThreadFactory +{ + class CAddrPoolHandler : public CInterface, implements IPooledThread + { + public: + IMPLEMENT_IINTERFACE; + + CAddrThreadArgs *args; + + virtual void init(void *param) override + { + args = (CAddrThreadArgs *)param; + } + + virtual void threadmain() override + { + StringBuffer name(args->name); + unsigned timeoutms = args->timeoutms; + + SocketEndpoint ep; + CCycleTimer timer; + int srtn = ep.ipset(name.str(), timeoutms); + unsigned lookupTimeMS = timer.elapsedMs(); + + StringBuffer ipstr; + if (args->logIt && srtn) + ep.getIpText(ipstr); + else if (!srtn) + ipstr.append("failed"); + if ((args->logIt && srtn) || (!srtn)) + { + DBGLOG("%s (%d) -> %s (%u ms)", name.str(), (int)timeoutms, ipstr.str(), lookupTimeMS); + fflush(NULL); + } + } + + virtual bool stop() override + { + return true; + } + + virtual bool canReuse() const override + { + return true; + } + }; + +public: + IMPLEMENT_IINTERFACE; + + IPooledThread *createNew() + { + return new CAddrPoolHandler(); + } +}; + +class getaddrinfotest : public CppUnit::TestFixture +{ +public: + CPPUNIT_TEST_SUITE(getaddrinfotest); + CPPUNIT_TEST(testaddr); + CPPUNIT_TEST_SUITE_END(); + +/* + * can change settings with: + * + * + * + * + * global: + * expert: + * disableDNSTimeout: true + * maxDNSThreads: 100 + */ + + void testaddr1(const char *_name, unsigned timeoutms, bool logIt=true) + { + StringBuffer name(_name); + + SocketEndpoint ep; + CCycleTimer timer; + int srtn = ep.ipset(name.str(), timeoutms); + unsigned lookupTimeMS = timer.elapsedMs(); + + StringBuffer ipstr; + if (logIt && srtn) + ep.getIpText(ipstr); + else if (!srtn) + ipstr.append("failed"); + if ((logIt && srtn) || (!srtn)) + { + DBGLOG("%s (%d) -> %s (%u ms)", name.str(), (int)timeoutms, ipstr.str(), lookupTimeMS); + fflush(NULL); + } + } + + void testaddr() + { + fflush(NULL); + DBGLOG(" "); // to get past the "." ... + fflush(NULL); + + testaddr1("google.com", 3); + testaddr1("google.com", 500); + + Owned threadFactory = new CAddrPoolFactory(); + Owned threadPool = createThreadPool("GetAddrThreadPool", threadFactory, true, nullptr, 60); + + // ----------------- + + Owned t1a = new CAddrThreadArgs("mck1.com", 5); + Owned t2a = new CAddrThreadArgs("mck1.com", 5000); + Owned t3a = new CAddrThreadArgs("mck1.com", INFINITE); + + Owned t1b = new CAddrThreadArgs("mck101.com", 5); + Owned t2b = new CAddrThreadArgs("mck101.com", 5000); + Owned t3b = new CAddrThreadArgs("mck101.com", INFINITE); + + Owned t1c = new CAddrThreadArgs("google.com", 3); + Owned t2c = new CAddrThreadArgs("google.com", 500); + Owned t3c = new CAddrThreadArgs("google.com", 10000); + + Owned t1d = new CAddrThreadArgs("localhost", 3); + Owned t2d = new CAddrThreadArgs("localhost", 500); + Owned t3d = new CAddrThreadArgs("localhost", 1000); + + Owned t1e = new CAddrThreadArgs("127.0.0.1", 2000); + Owned t2e = new CAddrThreadArgs("1.2.3.4", 2000); + + Owned t1f = new CAddrThreadArgs("mck2.com", INFINITE); + + Owned t1g = new CAddrThreadArgs("mck103.com", INFINITE); + + Owned t1h = new CAddrThreadArgs("*bogus+", INFINITE); + + // ----------------- + + threadPool->startNoBlock(t1a); + threadPool->startNoBlock(t2a); + threadPool->startNoBlock(t3a); + + threadPool->startNoBlock(t1b); + threadPool->startNoBlock(t2b); + threadPool->startNoBlock(t3b); + + threadPool->startNoBlock(t1c); + threadPool->startNoBlock(t2c); + threadPool->startNoBlock(t3c); + + threadPool->startNoBlock(t1d); + threadPool->startNoBlock(t2d); + threadPool->startNoBlock(t3d); + + threadPool->startNoBlock(t1e); + threadPool->startNoBlock(t2e); + + threadPool->startNoBlock(t1f); + + threadPool->startNoBlock(t1g); + + threadPool->startNoBlock(t1h); + + threadPool->joinAll(); + + fflush(NULL); + + threadPool->startNoBlock(t1c); + threadPool->startNoBlock(t2c); + threadPool->startNoBlock(t3c); + + threadPool->joinAll(true); + + fflush(NULL); + + // --------------- + + CCycleTimer timer; + for (int i=0; i<10000; i++) + { + testaddr1("google.com", 500, false); + } + unsigned lookupTimeMS = timer.elapsedMs(); + DBGLOG("10k lookups (same thread) time = %u ms", lookupTimeMS); + fflush(NULL); + + Owned threadPool1 = createThreadPool("GetAddrThreadPool1", threadFactory, true, nullptr, 1); + + timer.reset(); + Owned t10a = new CAddrThreadArgs("google.com", 500, false); + for (int i=0; i<10000; i++) + { + threadPool1->start(t10a, "threadpool-test", 99999999); + } + + threadPool1->joinAll(true); + + lookupTimeMS = timer.elapsedMs(); + fflush(NULL); + DBGLOG("10k lookups (threadpool of 1) time = %u ms", lookupTimeMS); + + fflush(NULL); + DBGLOG("testaddr complete"); + fflush(NULL); + Sleep(7000); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION( getaddrinfotest ); +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( getaddrinfotest, "getaddrinfotest" ); + #endif // _USE_CPPUNIT diff --git a/thorlcr/activities/funnel/thfunnelslave.cpp b/thorlcr/activities/funnel/thfunnelslave.cpp index 91deee8d18f..32962951c52 100644 --- a/thorlcr/activities/funnel/thfunnelslave.cpp +++ b/thorlcr/activities/funnel/thfunnelslave.cpp @@ -41,7 +41,7 @@ class CParallelFunnel : implements IRowStream, public CSimpleInterface StringAttr idStr; unsigned inputIndex; rowcount_t readThisInput; // purely for tracing - bool stopping; + std::atomic stopping{false}; public: CInputHandler(CParallelFunnel &_funnel, unsigned _inputIndex) : threaded("CInputHandler", this), funnel(_funnel), inputIndex(_inputIndex) @@ -63,8 +63,6 @@ class CParallelFunnel : implements IRowStream, public CSimpleInterface } void stop() { - CriticalBlock b(stopCrit); - if (stopping) return; stopping = true; } void join() @@ -77,6 +75,9 @@ class CParallelFunnel : implements IRowStream, public CSimpleInterface { bool started = false; IEngineRowStream *inputStream = nullptr; + constexpr unsigned chunkSize = 32; + const void * rows[chunkSize]; + unsigned numRows = 0; try { funnel.activity.startInput(inputIndex); @@ -84,20 +85,26 @@ class CParallelFunnel : implements IRowStream, public CSimpleInterface inputStream = funnel.activity.queryInputStream(inputIndex); while (!stopping) { - OwnedConstThorRow row = inputStream->ungroupedNextRow(); - if (!row) break; - + numRows = 0; + for (;numRows < chunkSize; numRows++) { - CriticalBlock b(stopCrit); - if (stopping) break; + const void * row = inputStream->ungroupedNextRow(); + if (!row) + break; + rows[numRows] = row; } - CriticalBlock b(funnel.crit); // will mean first 'push' could block on fullSem, others on this crit. - funnel.push(row.getClear()); - ++readThisInput; + + if (numRows == 0) break; + + funnel.pushMulti(numRows, rows); + readThisInput += numRows; + if (numRows != chunkSize) + break; } } catch (IException *e) { + roxiemem::ReleaseRoxieRowArray(numRows, rows); funnel.fireException(e); e->Release(); } @@ -124,27 +131,73 @@ class CParallelFunnel : implements IRowStream, public CSimpleInterface unsigned eoss; StringAttr idStr; - CriticalSection fullCrit, crit; + CriticalSection crit; + CriticalSection writerCrit; SimpleInterThreadQueueOf rows; Semaphore fullSem; size32_t totSize; - bool full, stopped; + unsigned waiting = 0; + bool stopped; Linked serializer; void push(const void *row) { - CriticalBlock b2(fullCrit); // exclusivity for totSize / full - if (stopped) + size32_t rowSize = thorRowMemoryFootprint(serializer, row); + + bool waitForSpace = false; + // only allow a single writer at a time, so only a single thread is waiting on the semaphore - otherwise signal() takes a very long time { - ReleaseThorRow(row); - return; + + CriticalBlock b(crit); // will mean first 'push' could block on fullSem, others on this crit. + if (stopped) + { + ReleaseThorRow(row); + return; + } + rows.enqueue(row); + totSize += rowSize; + if (totSize > FUNNEL_MIN_BUFF_SIZE) + { + waiting++; + waitForSpace = true; + } } - rows.enqueue(row); - totSize += thorRowMemoryFootprint(serializer, row); - while (totSize > FUNNEL_MIN_BUFF_SIZE) + + if (waitForSpace) { - full = true; - CriticalUnblock b(fullCrit); + CriticalBlock b(writerCrit); + fullSem.wait(); // block pushers on crit + } + } + + void pushMulti(unsigned numRows, const void * * newRows) + { + size32_t rowSizes = 0; + for (unsigned i=0; i < numRows; i++) + rowSizes += thorRowMemoryFootprint(serializer, newRows[i]); + + bool waitForSpace = false; + // only allow a single writer at a time, so only a single thread is waiting on the semaphore - otherwise signal() takes a very long time + { + CriticalBlock b(crit); // will mean first 'push' could block on fullSem, others on this crit. + if (stopped) + { + for (unsigned i=0; i < numRows; i++) + ReleaseThorRow(newRows[i]); + return; + } + rows.enqueueMany(numRows, newRows); + totSize += rowSizes; + if (totSize > FUNNEL_MIN_BUFF_SIZE) + { + waiting++; + waitForSpace = true; + } + } + + if (waitForSpace) + { + CriticalBlock b(writerCrit); fullSem.wait(); // block pushers on crit } } @@ -168,7 +221,8 @@ class CParallelFunnel : implements IRowStream, public CSimpleInterface { idStr.set(activityKindStr(activity.queryContainer().getKind())); - stopped = full = false; + stopped = false; + waiting = 0; totSize = 0; eoss = 0; serializer.set(activity.queryRowSerializer()); @@ -210,10 +264,11 @@ class CParallelFunnel : implements IRowStream, public CSimpleInterface CInputHandler &handler = inputHandlers.item(h); handler.stop(); } + { - CriticalBlock b(fullCrit); + CriticalBlock b(crit); stopped = true; // ensure any pending push()'s don't enqueue and if big row potentially block again. - if (full) + if (waiting) { for (;;) { @@ -222,7 +277,8 @@ class CParallelFunnel : implements IRowStream, public CSimpleInterface } rows.stop(); // I don't think really needed totSize = 0; - fullSem.signal(); + fullSem.signal(waiting); + waiting = 0; } } ForEachItemIn(h2, inputHandlers) @@ -243,16 +299,19 @@ class CParallelFunnel : implements IRowStream, public CSimpleInterface return NULL; } size32_t sz = thorRowMemoryFootprint(serializer, row.get()); + unsigned numToSignal = 0; { - CriticalBlock b(fullCrit); + CriticalBlock b(crit); assertex(totSize>=sz); totSize -= sz; - if (full) + if (waiting && (totSize <= FUNNEL_MIN_BUFF_SIZE)) { - full = false; - fullSem.signal(); + numToSignal = 1; + waiting--; } } + if (numToSignal) + fullSem.signal(numToSignal); return row.getClear(); } diff --git a/thorlcr/activities/hashdistrib/thhashdistribslave.cpp b/thorlcr/activities/hashdistrib/thhashdistribslave.cpp index 61a49ced9a9..da214c0f16a 100644 --- a/thorlcr/activities/hashdistrib/thhashdistribslave.cpp +++ b/thorlcr/activities/hashdistrib/thhashdistribslave.cpp @@ -2760,26 +2760,11 @@ class CSpill : implements IRowWriter, public CSimpleInterface ::Release(writer); writer = NULL; spillFileIO->flush(); - mergeStats(stats, this); - spillFile->noteSize(getStatistic(StSizeSpillFile)); + mergeRemappedStats(stats, spillFileIO, diskToTempStatsMap); + stats.addStatistic(StNumSpills, 1); + spillFile->noteSize(spillFileIO->getStatistic(StSizeDiskWrite)); spillFileIO.clear(); } - inline __int64 getStatistic(StatisticKind kind) const - { - switch (kind) - { - case StSizeSpillFile: - return spillFileIO->getStatistic(StSizeDiskWrite); - case StTimeSortElapsed: - return spillFileIO->getStatistic(StTimeDiskWriteIO); - case StSizeDiskWrite: - return 0; // Return file size as StSizeSpillFile kind. To avoid confusion, StSizeDiskWrite will not be returned - case StNumSpills: - return 1; - default: - return spillFileIO->getStatistic(kind); - } - } // IRowWriter virtual void putRow(const void *row) override { diff --git a/thorlcr/activities/loop/thloop.cpp b/thorlcr/activities/loop/thloop.cpp index b32da6e09ab..ebdde73be06 100644 --- a/thorlcr/activities/loop/thloop.cpp +++ b/thorlcr/activities/loop/thloop.cpp @@ -334,7 +334,7 @@ class CLocalResultActivityMasterBase : public CMasterActivity Owned inputRowIf; public: - CLocalResultActivityMasterBase(CMasterGraphElement *info) : CMasterActivity(info, spillingActivityStatistics) + CLocalResultActivityMasterBase(CMasterGraphElement *info) : CMasterActivity(info) { } virtual void init() override diff --git a/thorlcr/activities/loop/thloopslave.cpp b/thorlcr/activities/loop/thloopslave.cpp index ea7d149ff89..722358f0e4d 100644 --- a/thorlcr/activities/loop/thloopslave.cpp +++ b/thorlcr/activities/loop/thloopslave.cpp @@ -643,7 +643,7 @@ class CLocalResultSpillActivity : public CSlaveActivity } public: - CLocalResultSpillActivity(CGraphElementBase *_container) : CSlaveActivity(_container, spillingActivityStatistics) + CLocalResultSpillActivity(CGraphElementBase *_container) : CSlaveActivity(_container) { helper = (IHThorLocalResultSpillArg *)queryHelper(); appendOutputLinked(this); @@ -708,7 +708,7 @@ class CLocalResultSpillActivity : public CSlaveActivity class CLocalResultWriteActivityBase : public ProcessSlaveActivity { public: - CLocalResultWriteActivityBase(CGraphElementBase *_container) : ProcessSlaveActivity(_container, spillingActivityStatistics) + CLocalResultWriteActivityBase(CGraphElementBase *_container) : ProcessSlaveActivity(_container) { } virtual IThorResult *createResult() = 0; diff --git a/thorlcr/activities/nsplitter/thnsplitterslave.cpp b/thorlcr/activities/nsplitter/thnsplitterslave.cpp index 5331fedf845..ef47bcc27bf 100644 --- a/thorlcr/activities/nsplitter/thnsplitterslave.cpp +++ b/thorlcr/activities/nsplitter/thnsplitterslave.cpp @@ -152,7 +152,7 @@ class NSplitterSlaveActivity : public CSlaveActivity, implements ISharedSmartBuf } } public: - NSplitterSlaveActivity(CGraphElementBase *_container) : CSlaveActivity(_container, spillingActivityStatistics), writer(*this) + NSplitterSlaveActivity(CGraphElementBase *_container) : CSlaveActivity(_container), writer(*this) { numOutputs = container.getOutputs(); connectedOutputSet.setown(createBitSet()); diff --git a/thorlcr/master/thactivitymaster.cpp b/thorlcr/master/thactivitymaster.cpp index 86210a47c87..a99a0db2919 100644 --- a/thorlcr/master/thactivitymaster.cpp +++ b/thorlcr/master/thactivitymaster.cpp @@ -207,10 +207,8 @@ class CGenericMasterGraphElement : public CMasterGraphElement case TAKdistributed: case TAKtrace: case TAKemptyaction: - ret = new CMasterActivity(this); - break; case TAKsplit: - ret = new CMasterActivity(this, spillingActivityStatistics); + ret = new CMasterActivity(this); break; case TAKsoap_rowdataset: case TAKsoap_rowaction: diff --git a/thorlcr/thorutil/thbuf.cpp b/thorlcr/thorutil/thbuf.cpp index 42457777dae..2e45c7c78d8 100644 --- a/thorlcr/thorutil/thbuf.cpp +++ b/thorlcr/thorutil/thbuf.cpp @@ -668,7 +668,7 @@ static std::tuple createSerialOutputSt - Writer: - The writer to an in-memory queue, and when the queue is full, or a certain number of rows have been queued, it writes to starts writing to temp files. - The writer will always write to the queue if it can, even after it has started spilling. - - The writer commits to disk at LookAheadOptions::writeAheadSize granularity + - The writer commits to disk at LookAheadOptions::writeAheadSize granularity. NB: size is uncompressed, measured before data is written to disk. - The writer creates a new temp file when the current one reaches LookAheadOptions::tempFileGranularity - The writer pushes the current nextOutputRow to a queue when it creates the next output file (used by the reader to know when to move to next) - NB: writer implements ISmartRowBuffer::flush() which has slightly weird semantics (blocks until everything is read or stopped) @@ -706,7 +706,6 @@ class CCompressedSpillingRowStream: public CSimpleInterfaceOf, Owned outputStream; std::unique_ptr outputStreamSerializer; memsize_t pendingFlushToDiskSz = 0; - offset_t currentTempFileSize = 0; CFileOwner *currentOwnedOutputFile = nullptr; Owned currentOutputIFileIO; // keep for stats CriticalSection outputFilesQCS; @@ -876,15 +875,14 @@ class CCompressedSpillingRowStream: public CSimpleInterfaceOf, { if (pendingFlushToDiskSz <= threshold) return false; + pendingFlushToDiskSz = 0; rowcount_t currentNextOutputRow = nextOutputRow.load(); trace("WRITE: Flushed to disk. nextOutputRow = %" RCPF "u", currentNextOutputRow); outputStream->flush(); - currentTempFileSize += pendingFlushToDiskSz; + offset_t currentTempFileSize = currentOutputIFileIO->getStatistic(StSizeDiskWrite); currentOwnedOutputFile->noteSize(currentTempFileSize); - pendingFlushToDiskSz = 0; if (currentTempFileSize > options.tempFileGranularity) { - currentTempFileSize = 0; { CriticalBlock b(outputStreamCS); // set if reader isn't bounded yet, or queue next boundary @@ -899,7 +897,7 @@ class CCompressedSpillingRowStream: public CSimpleInterfaceOf, trace("WRITE: adding to tempFileEndRowMarker(size=%u): %" RCPF "u", (unsigned)outputFileEndRowMarkers.size(), currentNextOutputRow); } } - createNextOutputStream(); + createNextOutputStream(); // NB: creates new currentOwnedOutputFile/currentOutputIFileIO } committedRows = currentNextOutputRow; return true; @@ -2150,21 +2148,9 @@ class CSharedWriteAheadDisk : public CSharedWriteAheadBase } virtual unsigned __int64 getStatistic(StatisticKind kind) const override { - switch (kind) - { - case StSizeSpillFile: - return tempFileIO->getStatistic(StSizeDiskWrite); - case StCycleDiskWriteIOCycles: - case StTimeDiskWriteIO: - case StSizeDiskWrite: - return 0; - case StNumSpills: - return 1; - case StTimeSpillElapsed: - return tempFileIO->getStatistic(StCycleDiskWriteIOCycles); - default: - return tempFileIO->getStatistic(kind); - } + if (kind==StNumSpills) + return 1; + return tempFileIO->getStatistic(kind); } }; @@ -2464,6 +2450,7 @@ class CSharedFullSpillingWriteAhead : public CInterfaceOfflush(); tempFileOwner->noteSize(iFileIO->getStatistic(StSizeDiskWrite)); - ::mergeStats(inactiveStats, iFileIO); + updateRemappedStatsDelta(inactiveStats, previousFileStats, iFileIO, diskToTempStatsMap); // NB: also updates prev to current + previousFileStats.reset(); iFileIO.clear(); - outputStream.clear(); } } void createOutputStream() { + closeWriter(); // Ensure stats from closing files are preserved in inactiveStats // NB: Called once, when spilling starts. tempFileOwner.setown(activity.createOwnedTempFile(baseTmpFilename)); auto res = createSerialOutputStream(&(tempFileOwner->queryIFile()), compressHandler, options, numOutputs + 1); outputStream.setown(std::get<0>(res)); iFileIO.setown(std::get<1>(res)); totalInputRowsRead = inMemTotalRows; + inactiveStats.addStatistic(StNumSpills, 1); } void writeRowsFromInput() { @@ -2549,6 +2539,7 @@ class CSharedFullSpillingWriteAhead : public CInterfaceOfflush(); totalInputRowsRead.fetch_add(newRowsWritten); tempFileOwner->noteSize(iFileIO->getStatistic(StSizeDiskWrite)); + updateRemappedStatsDelta(inactiveStats, previousFileStats, iFileIO, diskToTempStatsMap); // NB: also updates prev to current // JCSMORE - could track size written, and start new file at this point (e.g. every 100MB), // and track their starting points (by row #) in a vector // We could then tell if/when the readers catch up, and remove consumed files as they do. @@ -2562,7 +2553,7 @@ class CSharedFullSpillingWriteAhead : public CInterfaceOfqueryRowMetaData()), serializer(rowIf->queryRowSerializer()), allocator(rowIf->queryRowAllocator()), deserializer(rowIf->queryRowDeserializer()), - inactiveStats(spillingWriteAheadStatistics) + inactiveStats(spillStatistics), previousFileStats(spillStatistics) { assertex(input); @@ -2726,29 +2717,7 @@ class CSharedFullSpillingWriteAhead : public CInterfaceOfgetStatistic(useKind); - v += inactiveStats.getStatisticValue(useKind); - return v; + return inactiveStats.getStatisticValue(kind); } }; diff --git a/thorlcr/thorutil/thormisc.cpp b/thorlcr/thorutil/thormisc.cpp index 0781cee53d4..fa2a6164204 100644 --- a/thorlcr/thorutil/thormisc.cpp +++ b/thorlcr/thorutil/thormisc.cpp @@ -74,8 +74,9 @@ static Owned ClusterMPAllocator; // stat. mappings shared between master and slave activities const StatisticsMapping spillStatistics({StTimeSpillElapsed, StTimeSortElapsed, StNumSpills, StSizeSpillFile, StSizePeakTempDisk}); +const StatisticsMapping executeStatistics({StTimeTotalExecute, StTimeLocalExecute, StTimeBlocked}); const StatisticsMapping soapcallStatistics({StTimeSoapcall}); -const StatisticsMapping basicActivityStatistics({StTimeTotalExecute, StTimeLocalExecute, StTimeBlocked, StNumParallelExecute}); +const StatisticsMapping basicActivityStatistics({StNumParallelExecute}, executeStatistics, spillStatistics); const StatisticsMapping groupActivityStatistics({StNumGroups, StNumGroupMax}, basicActivityStatistics); const StatisticsMapping indexReadFileStatistics({}, diskReadRemoteStatistics, jhtreeCacheStatistics); const StatisticsMapping indexReadActivityStatistics({StNumRowsProcessed}, indexReadFileStatistics, basicActivityStatistics); @@ -89,15 +90,18 @@ const StatisticsMapping joinActivityStatistics({StNumLeftRows, StNumRightRows}, const StatisticsMapping diskReadActivityStatistics({StNumDiskRowsRead, }, basicActivityStatistics, diskReadRemoteStatistics); const StatisticsMapping diskWriteActivityStatistics({StPerReplicated}, basicActivityStatistics, diskWriteRemoteStatistics); const StatisticsMapping sortActivityStatistics({}, basicActivityStatistics, spillStatistics); -const StatisticsMapping graphStatistics({StNumExecutions, StSizeSpillFile, StSizeGraphSpill, StSizePeakTempDisk, StSizePeakEphemeralDisk, StTimeUser, StTimeSystem, StNumContextSwitches, StSizeMemory, StSizePeakMemory, StSizeRowMemory, StSizePeakRowMemory}, basicActivityStatistics); const StatisticsMapping diskReadPartStatistics({StNumDiskRowsRead}, diskReadRemoteStatistics); const StatisticsMapping indexDistribActivityStatistics({}, basicActivityStatistics, jhtreeCacheStatistics); const StatisticsMapping soapcallActivityStatistics({}, basicActivityStatistics, soapcallStatistics); const StatisticsMapping hashDedupActivityStatistics({}, spillStatistics, diskWriteRemoteStatistics, basicActivityStatistics); const StatisticsMapping hashDistribActivityStatistics({StNumLocalRows, StNumRemoteRows, StSizeRemoteWrite}, basicActivityStatistics); -const StatisticsMapping spillingActivityStatistics({}, spillStatistics, basicActivityStatistics); -const StatisticsMapping spillingWriteAheadStatistics({}, spillStatistics); -const StatisticsMapping loopActivityStatistics({StNumIterations}, spillingActivityStatistics); +const StatisticsMapping loopActivityStatistics({StNumIterations}, basicActivityStatistics); +const StatisticsMapping graphStatistics({StNumExecutions, StSizeSpillFile, StSizeGraphSpill, StSizePeakTempDisk, StSizePeakEphemeralDisk, StTimeUser, StTimeSystem, StNumContextSwitches, StSizeMemory, StSizePeakMemory, StSizeRowMemory, StSizePeakRowMemory}, executeStatistics); + +const StatKindMap diskToTempStatsMap +={ {StSizeDiskWrite, StSizeSpillFile}, + {StTimeDiskWriteIO, StTimeSpillElapsed} + }; MODULE_INIT(INIT_PRIORITY_STANDARD) { diff --git a/thorlcr/thorutil/thormisc.hpp b/thorlcr/thorutil/thormisc.hpp index fc45b30e472..6c667ebd8e9 100644 --- a/thorlcr/thorutil/thormisc.hpp +++ b/thorlcr/thorutil/thormisc.hpp @@ -167,8 +167,9 @@ extern graph_decl const StatisticsMapping soapcallActivityStatistics; extern graph_decl const StatisticsMapping indexReadFileStatistics; extern graph_decl const StatisticsMapping hashDedupActivityStatistics; extern graph_decl const StatisticsMapping hashDistribActivityStatistics; -extern graph_decl const StatisticsMapping spillingActivityStatistics; -extern graph_decl const StatisticsMapping spillingWriteAheadStatistics; + +// Maps disk related stats to spill stats +extern graph_decl const std::map diskToTempStatsMap; class BooleanOnOff { diff --git a/tools/testsocket/testsocket.cpp b/tools/testsocket/testsocket.cpp index 221e4361a72..ebbaff42aa4 100644 --- a/tools/testsocket/testsocket.cpp +++ b/tools/testsocket/testsocket.cpp @@ -301,7 +301,7 @@ int readResults(ISocket * socket, bool readBlocked, bool useHTTP, StringBuffer & { try { - socket->read(t, 0, len, sendlen); + socket->read(t, 1, len, sendlen); } catch (IException *E) {