Compare commits
452 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41f0cfe867 | ||
|
|
ba091f1c62 | ||
|
|
21ac3825a6 | ||
|
|
a07bd8c0f5 | ||
|
|
33ee92c7d9 | ||
|
|
74f7c3a0cd | ||
|
|
656152f1f1 | ||
|
|
c7adc690e4 | ||
|
|
36b97bd5a7 | ||
|
|
f9bd4980ee | ||
|
|
75a2aae6b3 | ||
|
|
5da588afbe | ||
|
|
d8c8291886 | ||
|
|
e7b81da50b | ||
|
|
f1b9eb8d4c | ||
|
|
4639fbfb64 | ||
|
|
4942b4346b | ||
|
|
fc1de34374 | ||
|
|
ade0841d41 | ||
|
|
ee59941ca3 | ||
|
|
0fbcfa7eba | ||
|
|
047a3e9922 | ||
|
|
0ec870fd77 | ||
|
|
56d2dc6392 | ||
|
|
82e48a0908 | ||
|
|
1320b03bc5 | ||
|
|
1f512433ef | ||
|
|
fd87df5fed | ||
|
|
fa0bc023fb | ||
|
|
f5f7bbb57f | ||
|
|
57cad9c0a8 | ||
|
|
b039c8e927 | ||
|
|
1c25870679 | ||
|
|
83a49569ab | ||
|
|
6fdb4d2416 | ||
|
|
9620d43700 | ||
|
|
2c5ff070b3 | ||
|
|
59436578ee | ||
|
|
e66ffb1556 | ||
|
|
1333e3d2ea | ||
|
|
fcb1814a0c | ||
|
|
9f5b2fa537 | ||
|
|
095511b4eb | ||
|
|
76c108bf3c | ||
|
|
096f066024 | ||
|
|
52171bd90c | ||
|
|
c729e35ddb | ||
|
|
c44f2c6c3c | ||
|
|
00d351c85c | ||
|
|
c39cc1b0ca | ||
|
|
f1674d26de | ||
|
|
cbb0166e7c | ||
|
|
4bb770f31a | ||
|
|
282e31f5fc | ||
|
|
704a4272dd | ||
|
|
b34c595eb0 | ||
|
|
84f74d8ad6 | ||
|
|
6a5393a925 | ||
|
|
b81f8065d7 | ||
|
|
13d68879d3 | ||
|
|
96d0d1c668 | ||
|
|
0566e21f93 | ||
|
|
34ed6c3512 | ||
|
|
9447ce07ea | ||
|
|
f169ae654c | ||
|
|
9a7a293a84 | ||
|
|
d836f78e41 | ||
|
|
f15bc7396e | ||
|
|
9c413c7f11 | ||
|
|
5d88023dd1 | ||
|
|
9e7b09ff0a | ||
|
|
d5783a1545 | ||
|
|
2b43e08dca | ||
|
|
11d19baa2e | ||
|
|
df1fe4c05f | ||
|
|
1ec733a3f8 | ||
|
|
4e8ba65838 | ||
|
|
e58e8d3989 | ||
|
|
95ff757c73 | ||
|
|
70670d7217 | ||
|
|
87cdeaadb6 | ||
|
|
18b5f287f7 | ||
|
|
1cab23c1f1 | ||
|
|
89383695ed | ||
|
|
8b2b62bd6f | ||
|
|
375b5d1b7c | ||
|
|
980f98299f | ||
|
|
0868ee6beb | ||
|
|
a78edf0703 | ||
|
|
2bbaf87fea | ||
|
|
9f688af4fb | ||
|
|
c927614964 | ||
|
|
ec8e44de5a | ||
|
|
f4422f5976 | ||
|
|
afd30f2cfa | ||
|
|
51fba26f78 | ||
|
|
736411c113 | ||
|
|
1fa5c2c31a | ||
|
|
9ce2f7df4c | ||
|
|
68dc4f54a5 | ||
|
|
c8e50b8011 | ||
|
|
a9a0140476 | ||
|
|
84e512c2e4 | ||
|
|
d9c00c452b | ||
|
|
2515892217 | ||
|
|
c7253129b5 | ||
|
|
e7a61ce8f8 | ||
|
|
9e87ac2bf9 | ||
|
|
38a11d1e1f | ||
|
|
a894cace6b | ||
|
|
6561063f52 | ||
|
|
0e5673f2dc | ||
|
|
df53cfeb85 | ||
|
|
8e00a8d41d | ||
|
|
10c5272497 | ||
|
|
e9d32ad23d | ||
|
|
26854a6a3d | ||
|
|
03bc114f93 | ||
|
|
9bf5786bde | ||
|
|
c5b8e34cd3 | ||
|
|
79ea507664 | ||
|
|
8fd2a69378 | ||
|
|
8ee91caacf | ||
|
|
4df8df2f17 | ||
|
|
ac28919390 | ||
|
|
f25e81c015 | ||
|
|
ab54061365 | ||
|
|
1720ba3e81 | ||
|
|
4dc418b8d3 | ||
|
|
7b2acc80be | ||
|
|
b81073cb1a | ||
|
|
f24dca312f | ||
|
|
b38ce2a79d | ||
|
|
64591671fc | ||
|
|
116e7aab24 | ||
|
|
c48dfe5d4d | ||
|
|
bb5f3651b2 | ||
|
|
401894a53b | ||
|
|
0c7a7236eb | ||
|
|
84e6f5fa1d | ||
|
|
d3898d9b7f | ||
|
|
a1d7c768bb | ||
|
|
d0b5abfc46 | ||
|
|
1da3d34299 | ||
|
|
3be3dfedf6 | ||
|
|
96e534bddc | ||
|
|
0cf13d3bf4 | ||
|
|
09c9db306a | ||
|
|
12c56318a8 | ||
|
|
8180845429 | ||
|
|
0e6826b7e4 | ||
|
|
0bc181c216 | ||
|
|
2c23688be6 | ||
|
|
d3846adaa2 | ||
|
|
70428fb762 | ||
|
|
82d592702d | ||
|
|
c3c8f112ee | ||
|
|
e636a6db45 | ||
|
|
c68f6a7a89 | ||
|
|
a8f7f78fe1 | ||
|
|
1b741dd3b3 | ||
|
|
452a190094 | ||
|
|
75caf1b0e7 | ||
|
|
0dcec25d7e | ||
|
|
348b94bdee | ||
|
|
d091a730fb | ||
|
|
c3b3c3033f | ||
|
|
c5ed576b1e | ||
|
|
fbc493c361 | ||
|
|
b972cebad4 | ||
|
|
849d48e858 | ||
|
|
9b47c90de9 | ||
|
|
d19413f8e6 | ||
|
|
3704082f2a | ||
|
|
38df7114f2 | ||
|
|
28106d9df4 | ||
|
|
30da51bc72 | ||
|
|
e5af2d3133 | ||
|
|
1fd451510f | ||
|
|
921f5c4b2e | ||
|
|
b6f135b4c0 | ||
|
|
aec312006e | ||
|
|
5166914162 | ||
|
|
ed9f836977 | ||
|
|
9aeb8135e0 | ||
|
|
66b5d8aa07 | ||
|
|
a497eb5fcd | ||
|
|
b005f42b2e | ||
|
|
1c0774b1a9 | ||
|
|
48a5609de2 | ||
|
|
ed0532bcf7 | ||
|
|
a016d13c15 | ||
|
|
428f438e4f | ||
|
|
1d5dc9e5e8 | ||
|
|
c8405e95f3 | ||
|
|
9842d5754f | ||
|
|
53aa4d2f03 | ||
|
|
ce6ddc74d7 | ||
|
|
c833612414 | ||
|
|
84ceee07bf | ||
|
|
a9cb897af4 | ||
|
|
a822b35d87 | ||
|
|
29cc000c08 | ||
|
|
ab2f0f0bbc | ||
|
|
0ddf9a460d | ||
|
|
571baeead0 | ||
|
|
daf986972b | ||
|
|
8aac13eb4d | ||
|
|
36e41d6b7f | ||
|
|
88748a50f3 | ||
|
|
807c353fc0 | ||
|
|
82ec9dc608 | ||
|
|
1f7cd2cba5 | ||
|
|
1e68d49193 | ||
|
|
83bcb31994 | ||
|
|
39392705a7 | ||
|
|
f4b4ab3d74 | ||
|
|
8fa63c21f9 | ||
|
|
1c3235b402 | ||
|
|
b0fec0b09d | ||
|
|
342d59a19d | ||
|
|
ea978c5770 | ||
|
|
5fb80085fb | ||
|
|
1ba4392648 | ||
|
|
1270c70499 | ||
|
|
90d30d9a63 | ||
|
|
c1db2380bd | ||
|
|
bb8b7a6702 | ||
|
|
aa05180325 | ||
|
|
018b4fc6f7 | ||
|
|
b44bc1cabd | ||
|
|
3be8cb6323 | ||
|
|
022bed651c | ||
|
|
337b657f9c | ||
|
|
04825c4b70 | ||
|
|
2cca516445 | ||
|
|
e4324ead54 | ||
|
|
2224b715be | ||
|
|
254ad97805 | ||
|
|
834caf09bf | ||
|
|
27bb4831f6 | ||
|
|
11d27e3e52 | ||
|
|
c20f0c3d97 | ||
|
|
c35bbf1bd2 | ||
|
|
ffecd115fe | ||
|
|
3b5cf48e5b | ||
|
|
5eb24c7e63 | ||
|
|
aa9eff357c | ||
|
|
90910f6ded | ||
|
|
f59097ab16 | ||
|
|
9f3f32391c | ||
|
|
e66c5fa67d | ||
|
|
7cfdea2053 | ||
|
|
eed4773a4f | ||
|
|
4a7d515d94 | ||
|
|
4be79ff78c | ||
|
|
2633de8b53 | ||
|
|
0bf71e15db | ||
|
|
c237634847 | ||
|
|
ca2bb3cec6 | ||
|
|
0749acb7e6 | ||
|
|
95131fe376 | ||
|
|
3de310c951 | ||
|
|
4f3105f4a4 | ||
|
|
24ee05d15f | ||
|
|
ba6f4c2e8d | ||
|
|
b102163220 | ||
|
|
897ddb64ce | ||
|
|
7b20bdacdf | ||
|
|
0ac93c8674 | ||
|
|
06d40edb0a | ||
|
|
fd7489f6da | ||
|
|
89f6bed3b8 | ||
|
|
2001518ec5 | ||
|
|
8a0f1dd730 | ||
|
|
0dfcb6153a | ||
|
|
31d947e971 | ||
|
|
7a2f3715f9 | ||
|
|
84199e5328 | ||
|
|
2989274f1f | ||
|
|
ee9dcde37b | ||
|
|
c278251d8f | ||
|
|
3252809448 | ||
|
|
defcb52571 | ||
|
|
8bc5730532 | ||
|
|
58e6248874 | ||
|
|
3c99f26824 | ||
|
|
afaa577b1a | ||
|
|
b4f0ebbaab | ||
|
|
4925532c25 | ||
|
|
3e9da68537 | ||
|
|
d8166a558d | ||
|
|
c57b04c4c8 | ||
|
|
1851ba7eb5 | ||
|
|
208fd70d58 | ||
|
|
93ccd2a145 | ||
|
|
d8a00b2c8b | ||
|
|
9be6413e5c | ||
|
|
b75e6b43cd | ||
|
|
d95441a48d | ||
|
|
01ff608cfa | ||
|
|
6b137be127 | ||
|
|
6ec2104b81 | ||
|
|
8e637b482a | ||
|
|
14f11a9d0d | ||
|
|
6abdb73512 | ||
|
|
a3ba653690 | ||
|
|
8408c187d6 | ||
|
|
34b8e7d15e | ||
|
|
75229c6209 | ||
|
|
561119367a | ||
|
|
90f5247781 | ||
|
|
3980748099 | ||
|
|
34a5f9c9cf | ||
|
|
df89ddf059 | ||
|
|
04361155aa | ||
|
|
b440ade235 | ||
|
|
541126ead5 | ||
|
|
052032ee8b | ||
|
|
9f8606b612 | ||
|
|
5d118ebf6b | ||
|
|
f7e38e273c | ||
|
|
824b23afc6 | ||
|
|
5491729a3c | ||
|
|
e9587a0083 | ||
|
|
a838756c9a | ||
|
|
e2728cba7c | ||
|
|
c9a2a62248 | ||
|
|
2fa32d7db7 | ||
|
|
5130c52676 | ||
|
|
6aa090a43a | ||
|
|
014890d720 | ||
|
|
380e65d47e | ||
|
|
e715de9e1c | ||
|
|
9380c2a3d1 | ||
|
|
997a7408c7 | ||
|
|
36b0c4426a | ||
|
|
0034afbcba | ||
|
|
9461e3c2ae | ||
|
|
a1e41a1dc1 | ||
|
|
87981c105d | ||
|
|
05ebe83f2f | ||
|
|
0e1862d26d | ||
|
|
40ff6f637b | ||
|
|
937fa526b9 | ||
|
|
cfa7124e76 | ||
|
|
25946a5c8e | ||
|
|
d598402a3c | ||
|
|
0256738f23 | ||
|
|
60acf1e402 | ||
|
|
eafb9375d2 | ||
|
|
05b3d5fcb1 | ||
|
|
0df68c6235 | ||
|
|
c95ccee93f | ||
|
|
def3e84f12 | ||
|
|
7fd2c2a22d | ||
|
|
8a3d43daa9 | ||
|
|
81f298361c | ||
|
|
a4c562e4a5 | ||
|
|
b7af179809 | ||
|
|
b8693ae500 | ||
|
|
73b65fc99e | ||
|
|
878a1e16a8 | ||
|
|
badeb9a7f0 | ||
|
|
08a92a2347 | ||
|
|
ad55377cba | ||
|
|
ab8edb4de7 | ||
|
|
7de971ec20 | ||
|
|
54112379ff | ||
|
|
061c34749a | ||
|
|
084aa2a6f3 | ||
|
|
05bbc55569 | ||
|
|
aa70087df4 | ||
|
|
21cc85064c | ||
|
|
29f11170be | ||
|
|
4eb391decd | ||
|
|
19f9e1eff0 | ||
|
|
6a7b2fd8a4 | ||
|
|
7f9e9fd809 | ||
|
|
72d8f73dbe | ||
|
|
f423f426a2 | ||
|
|
bcd0c8eaa5 | ||
|
|
90fed9b9a7 | ||
|
|
12a3240656 | ||
|
|
aa737b3dcf | ||
|
|
efaf601e85 | ||
|
|
b71e66003e | ||
|
|
39f96f93a9 | ||
|
|
ef23b96c94 | ||
|
|
bf371b21b9 | ||
|
|
d97045bc06 | ||
|
|
72171e5751 | ||
|
|
a133545d4b | ||
|
|
202ddaffab | ||
|
|
fcfb194c54 | ||
|
|
871cbcfeec | ||
|
|
e128906890 | ||
|
|
b510a323f2 | ||
|
|
e6eda8c31d | ||
|
|
778c155b5c | ||
|
|
93158f05a7 | ||
|
|
418946b0e1 | ||
|
|
8323701b27 | ||
|
|
6ab61f2fed | ||
|
|
c8bc4c9d3a | ||
|
|
1db5521027 | ||
|
|
90d29edcce | ||
|
|
685e6bf87d | ||
|
|
ae56eee88f | ||
|
|
2ab19c5948 | ||
|
|
e605063da6 | ||
|
|
4bb09ffd63 | ||
|
|
35aa4d30a4 | ||
|
|
a01c36ec1f | ||
|
|
f781aab119 | ||
|
|
8b2926a92f | ||
|
|
08ba28539f | ||
|
|
d9c247e5ed | ||
|
|
fcdb824f86 | ||
|
|
789d5bb610 | ||
|
|
baa16f5e87 | ||
|
|
a1e7249750 | ||
|
|
525b8ff2f9 | ||
|
|
81b25986e1 | ||
|
|
874474ddab | ||
|
|
95acc573f5 | ||
|
|
8cc6d61d39 | ||
|
|
e184e6c117 | ||
|
|
e2ae8dad02 | ||
|
|
03a8573a03 | ||
|
|
67a477f471 | ||
|
|
81b2334a31 | ||
|
|
72b3f6ea4d | ||
|
|
1f8116b21d | ||
|
|
4d025b1c9b | ||
|
|
79134758a8 | ||
|
|
f419509029 | ||
|
|
82ffc0b008 | ||
|
|
1ca90a925a | ||
|
|
9692d58195 | ||
|
|
c7769ea299 | ||
|
|
027807468a | ||
|
|
eafc9d2caa | ||
|
|
5ca1c098bc | ||
|
|
cbd974f850 | ||
|
|
fbb8b4ce37 | ||
|
|
94846848b7 | ||
|
|
bdc8ef52e9 | ||
|
|
f5ab6870b2 | ||
|
|
8d93905760 | ||
|
|
91db058809 | ||
|
|
2dd7ef910f |
25 changed files with 8484 additions and 3169 deletions
112
.clang-format
112
.clang-format
|
|
@ -3,17 +3,41 @@ Language: Cpp
|
|||
# BasedOnStyle: Google
|
||||
AccessModifierOffset: -1
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignConsecutiveMacros: false
|
||||
AlignConsecutiveAssignments: false
|
||||
AlignConsecutiveDeclarations: false
|
||||
AlignArrayOfStructures: None
|
||||
AlignConsecutiveAssignments:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
PadOperators: true
|
||||
AlignConsecutiveBitFields:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveDeclarations:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveMacros:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
PadOperators: false
|
||||
AlignEscapedNewlines: Left
|
||||
AlignOperands: true
|
||||
AlignTrailingComments: true
|
||||
AlignOperands: Align
|
||||
AlignTrailingComments:
|
||||
Kind: Always
|
||||
OverEmptyLines: 0
|
||||
AllowAllArgumentsOnNextLine: true
|
||||
AllowAllConstructorInitializersOnNextLine: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: true
|
||||
AllowShortBlocksOnASingleLine: Never
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortEnumsOnASingleLine: true
|
||||
AllowShortFunctionsOnASingleLine: None # All
|
||||
AllowShortIfStatementsOnASingleLine: Never # WithoutElse
|
||||
AllowShortLambdasOnASingleLine: Inline # All
|
||||
|
|
@ -22,59 +46,80 @@ AlwaysBreakAfterDefinitionReturnType: None
|
|||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: true
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
# AttributeMacros:
|
||||
# - __capability
|
||||
BinPackArguments: true
|
||||
BinPackParameters: true
|
||||
BitFieldColonSpacing: Both
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: false
|
||||
AfterClass: false
|
||||
AfterControlStatement: false
|
||||
AfterControlStatement: Never
|
||||
AfterEnum: false
|
||||
AfterExternBlock: false
|
||||
AfterFunction: false
|
||||
AfterNamespace: false
|
||||
AfterObjCDeclaration: false
|
||||
AfterStruct: false
|
||||
AfterUnion: false
|
||||
AfterExternBlock: false
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
BeforeLambdaBody: false
|
||||
BeforeWhile: false
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: true
|
||||
SplitEmptyRecord: true
|
||||
SplitEmptyNamespace: true
|
||||
BreakAfterAttributes: Never
|
||||
# BreakAfterJavaFieldAnnotations: false
|
||||
BreakArrays: true
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeBraces: Attach
|
||||
BreakBeforeInheritanceComma: true # false
|
||||
BreakInheritanceList: BeforeComma # BeforeColon
|
||||
BreakBeforeConceptDeclarations: Always
|
||||
BreakBeforeInlineASMColon: OnlyMultiline
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializersBeforeComma: true # false
|
||||
BreakConstructorInitializers: BeforeComma # BeforeColon
|
||||
# BreakAfterJavaFieldAnnotations: false
|
||||
BreakInheritanceList: BeforeComma # BeforeColon
|
||||
BreakStringLiterals: true
|
||||
ColumnLimit: 120 # 80
|
||||
CommentPragmas: '^ IWYU pragma:'
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: true
|
||||
DeriveLineEnding: true
|
||||
DerivePointerAlignment: true
|
||||
DisableFormat: false
|
||||
EmptyLineAfterAccessModifier: Never
|
||||
EmptyLineBeforeAccessModifier: LogicalBlock
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
FixNamespaceComments: true
|
||||
ForEachMacros:
|
||||
- Q_FOREACH_THIS_LIST_MUST_BE_NON_EMPTY
|
||||
IncludeBlocks: Preserve
|
||||
#IndentCaseBlocks: false
|
||||
IncludeCategories:
|
||||
- Regex: '.*'
|
||||
Priority: 0
|
||||
IndentAccessModifiers: false
|
||||
IndentCaseBlocks: false
|
||||
IndentCaseLabels: true
|
||||
IndentExternBlock: AfterExternBlock
|
||||
IndentGotoLabels: true
|
||||
IndentPPDirectives: None
|
||||
IndentRequiresClause: true
|
||||
IndentWidth: 2
|
||||
IndentWrappedFunctionNames: false
|
||||
InsertBraces: false
|
||||
InsertNewlineAtEOF: false
|
||||
# InsertTrailingCommas: None
|
||||
IntegerLiteralSeparator:
|
||||
Binary: 0
|
||||
Decimal: 0
|
||||
Hex: 0
|
||||
# JavaScriptQuotes: Leave
|
||||
# JavaScriptWrapImports: true
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
LambdaBodyIndentation: Signature
|
||||
LineEnding: DeriveLF
|
||||
MacroBlockBegin: ''
|
||||
MacroBlockEnd: ''
|
||||
MaxEmptyLinesToKeep: 1
|
||||
|
|
@ -84,39 +129,66 @@ NamespaceIndentation: None
|
|||
# ObjCBreakBeforeNestedBlockParam: true
|
||||
# ObjCSpaceAfterProperty: false
|
||||
# ObjCSpaceBeforeProtocolList: true
|
||||
PackConstructorInitializers: NextLine
|
||||
PenaltyBreakAssignment: 2
|
||||
PenaltyBreakBeforeFirstCallParameter: 1
|
||||
PenaltyBreakComment: 300
|
||||
PenaltyBreakFirstLessLess: 120
|
||||
PenaltyBreakOpenParenthesis: 0
|
||||
PenaltyBreakString: 1000
|
||||
PenaltyBreakTemplateDeclaration: 10
|
||||
PenaltyExcessCharacter: 1000000
|
||||
PenaltyIndentedWhitespace: 0
|
||||
PenaltyReturnTypeOnItsOwnLine: 200
|
||||
PointerAlignment: Left
|
||||
PointerAlignment: Right # Left
|
||||
PPIndentWidth: -1
|
||||
QualifierAlignment: Leave
|
||||
ReferenceAlignment: Pointer
|
||||
ReflowComments: false # true
|
||||
SortIncludes: false # disabled, because we need case insensitive sort
|
||||
SortUsingDeclarations: false # true
|
||||
RemoveBracesLLVM: false
|
||||
RemoveSemicolon: false
|
||||
RequiresClausePosition: OwnLine
|
||||
RequiresExpressionIndentation: OuterScope
|
||||
SeparateDefinitionBlocks: Leave
|
||||
ShortNamespaceLines: 0 # 1
|
||||
SortIncludes: CaseInsensitive # CaseSensitive
|
||||
# SortJavaStaticImport: Before
|
||||
SortUsingDeclarations: Lexicographic # LexicographicNumeric
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: true
|
||||
SpaceAroundPointerQualifiers: Default
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCaseColon: false
|
||||
SpaceBeforeCpp11BracedList: false
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceBeforeParensOptions:
|
||||
AfterControlStatements: true
|
||||
AfterForeachMacros: true
|
||||
AfterFunctionDefinitionName: false
|
||||
AfterFunctionDeclarationName: false
|
||||
AfterIfMacros: true
|
||||
AfterOverloadedOperator: false
|
||||
AfterRequiresInClause: false
|
||||
AfterRequiresInExpression: false
|
||||
BeforeNonEmptyParentheses: false
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceBeforeSquareBrackets: false
|
||||
SpaceInEmptyBlock: false
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 2
|
||||
SpacesInAngles: false
|
||||
SpacesInAngles: Never
|
||||
SpacesInConditionalStatement: false
|
||||
SpacesInContainerLiterals: true
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInLineCommentPrefix:
|
||||
Minimum: 1
|
||||
Maximum: 1 # -1
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: Auto
|
||||
TabWidth: 100 # 8
|
||||
UseCRLF: false
|
||||
UseTab: Never
|
||||
...
|
||||
|
|
|
|||
|
|
@ -1,6 +1,17 @@
|
|||
cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR)
|
||||
|
||||
project(TelegramBotApi VERSION 5.0 LANGUAGES CXX)
|
||||
if (POLICY CMP0065)
|
||||
# do not export symbols from executables
|
||||
# affects compiler checks in project(), so must be set before it
|
||||
cmake_policy(SET CMP0065 NEW)
|
||||
endif()
|
||||
|
||||
project(TelegramBotApi VERSION 7.1 LANGUAGES CXX)
|
||||
|
||||
if (POLICY CMP0069)
|
||||
option(TELEGRAM_BOT_API_ENABLE_LTO "Use \"ON\" to enable Link Time Optimization.")
|
||||
set(TD_ENABLE_LTO "${TELEGRAM_BOT_API_ENABLE_LTO}" CACHE STRING "Enable LTO" FORCE)
|
||||
endif()
|
||||
|
||||
add_subdirectory(td EXCLUDE_FROM_ALL)
|
||||
|
||||
|
|
@ -21,10 +32,27 @@ if (POLICY CMP0060)
|
|||
# link libraries by full path
|
||||
cmake_policy(SET CMP0060 NEW)
|
||||
endif()
|
||||
if (POLICY CMP0074)
|
||||
# use environment variables to find libraries
|
||||
cmake_policy(SET CMP0074 NEW)
|
||||
endif()
|
||||
|
||||
include(PreventInSourceBuild)
|
||||
prevent_in_source_build()
|
||||
|
||||
if (POLICY CMP0069 AND TELEGRAM_BOT_API_ENABLE_LTO)
|
||||
cmake_policy(SET CMP0069 NEW)
|
||||
include(CheckIPOSupported)
|
||||
check_ipo_supported(RESULT IPO_SUPPORTED LANGUAGES CXX)
|
||||
if (IPO_SUPPORTED)
|
||||
string(REPLACE ";" " " CXX_FLAGS_IPO "${CMAKE_CXX_COMPILE_OPTIONS_IPO}")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CXX_FLAGS_IPO}")
|
||||
|
||||
string(REPLACE ";" " " LINK_FLAGS_IPO "${CMAKE_CXX_LINK_OPTIONS_IPO}")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${LINK_FLAGS_IPO}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(CMAKE_THREAD_PREFER_PTHREAD ON)
|
||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||
find_package(Threads REQUIRED)
|
||||
|
|
@ -45,10 +73,13 @@ if (CLANG OR GCC)
|
|||
elseif (APPLE)
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-no_pie")
|
||||
endif()
|
||||
include(AddCXXCompilerFlag)
|
||||
add_cxx_compiler_flag("-static-libstdc++")
|
||||
add_cxx_compiler_flag("-static-libgcc")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(TG_HTTP_CLIENT_SOURCE
|
||||
set(TELEGRAM_BOT_API_SOURCE
|
||||
telegram-bot-api/telegram-bot-api.cpp
|
||||
|
||||
telegram-bot-api/Client.cpp
|
||||
|
|
@ -57,6 +88,7 @@ set(TG_HTTP_CLIENT_SOURCE
|
|||
telegram-bot-api/HttpStatConnection.cpp
|
||||
telegram-bot-api/Query.cpp
|
||||
telegram-bot-api/Stats.cpp
|
||||
telegram-bot-api/Watchdog.cpp
|
||||
telegram-bot-api/WebhookActor.cpp
|
||||
|
||||
telegram-bot-api/Client.h
|
||||
|
|
@ -67,14 +99,15 @@ set(TG_HTTP_CLIENT_SOURCE
|
|||
telegram-bot-api/HttpStatConnection.h
|
||||
telegram-bot-api/Query.h
|
||||
telegram-bot-api/Stats.h
|
||||
telegram-bot-api/Watchdog.h
|
||||
telegram-bot-api/WebhookActor.h
|
||||
)
|
||||
|
||||
add_executable(telegram-bot-api ${TG_HTTP_CLIENT_SOURCE})
|
||||
add_executable(telegram-bot-api ${TELEGRAM_BOT_API_SOURCE})
|
||||
target_include_directories(telegram-bot-api PRIVATE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
|
||||
target_link_libraries(telegram-bot-api PRIVATE memprof tdactor tdcore tddb tdnet tdutils)
|
||||
|
||||
install(TARGETS telegram-bot-api RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")
|
||||
if (MSVC AND VCPKG_TOOLCHAIN)
|
||||
install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>/" DESTINATION "${CMAKE_INSTALL_BINDIR}" FILES_MATCHING PATTERN "*.dll")
|
||||
install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>/" DESTINATION "${CMAKE_INSTALL_BINDIR}" FILES_MATCHING PATTERN "*.dll" PATTERN "*.pdb")
|
||||
endif()
|
||||
|
|
|
|||
25
Dockerfile
Normal file
25
Dockerfile
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
FROM alpine:3.19 as builder
|
||||
|
||||
RUN apk add gcc g++ make cmake ninja openssl-dev zlib-dev gperf linux-headers
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
COPY CMakeLists.txt .
|
||||
COPY td/ ./td/
|
||||
COPY telegram-bot-api ./telegram-bot-api/
|
||||
|
||||
RUN cmake -DCMAKE_BUILD_TYPE=Release -S . -B build -G Ninja
|
||||
RUN ninja -C build
|
||||
|
||||
FROM alpine:3.19 as final
|
||||
|
||||
RUN apk add openssl zlib gperf
|
||||
|
||||
WORKDIR /app
|
||||
COPY --from=builder /usr/src/app/build/telegram-bot-api .
|
||||
|
||||
ARG API_ID
|
||||
ARG API_HASH
|
||||
ARG HTTP_PORT
|
||||
ARG DIR
|
||||
ARG TEMP_DIR
|
||||
ENTRYPOINT /app/telegram-bot-api --api-id=$API_ID --api-hash=$API_HASH --local --http-port=$HTTP_PORT --dir=$DIR --temp-dir=$TEMP_DIR
|
||||
|
|
@ -23,7 +23,7 @@ If you do that, you'll only need to choose the target operating system to receiv
|
|||
|
||||
In general, you need to install all `Telegram Bot API server` [dependencies](#dependencies) and compile the source code using CMake:
|
||||
|
||||
```
|
||||
```sh
|
||||
git clone --recursive https://github.com/tdlib/telegram-bot-api.git
|
||||
cd telegram-bot-api
|
||||
mkdir build
|
||||
|
|
|
|||
340
build.html
340
build.html
|
|
@ -3,18 +3,185 @@
|
|||
|
||||
<head>
|
||||
<title>Telegram Bot API server build instructions</title>
|
||||
<style>
|
||||
.hide { display: none; }
|
||||
div.main { max-width:1200px; margin: auto; font-size: x-large; }
|
||||
select.large { font-size: large; }
|
||||
</style>
|
||||
<style>
|
||||
:root {
|
||||
--background: #fafafa;
|
||||
--color: black;
|
||||
--color-primary: #0088ff;
|
||||
--color-code-block: #ebf9ff;
|
||||
--color-select-border: rgb(211, 211, 211);
|
||||
--color-checkbox-background: rgb(211, 211, 211);
|
||||
--color-checkbox-tick: #ffffff;
|
||||
--color-copy-success-background: #c1ffc6;
|
||||
--color-copy-success-border: rgb(0, 255, 0);
|
||||
--color-copy-fail-background: #ffcbcb;
|
||||
--color-copy-fail-border: rgb(255, 0, 0);
|
||||
|
||||
color: var(--color);
|
||||
background: var(--background);
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: #0e0e0e;
|
||||
--color: rgb(190, 190, 190);
|
||||
--color-primary: #0088ff;
|
||||
--color-code-block: #101315;
|
||||
--color-select-border: rgb(54, 54, 54);
|
||||
--color-checkbox-background: rgb(51, 51, 51);
|
||||
--color-checkbox-tick: #ffffff;
|
||||
--color-copy-success-background: #001f00;
|
||||
--color-copy-success-border: rgb(0, 255, 0);
|
||||
--color-copy-fail-background: #1f0000;
|
||||
--color-copy-fail-border: rgb(255, 0, 0);
|
||||
}
|
||||
}
|
||||
body {
|
||||
font-family: 'Segoe UI', Arial, Helvetica, sans-serif;
|
||||
}
|
||||
.hide {
|
||||
display: none;
|
||||
}
|
||||
div.main {
|
||||
max-width: 1250px;
|
||||
padding: 25px;
|
||||
margin: auto;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
.main > div {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#buildCommands {
|
||||
font-family: Consolas, monospace;
|
||||
margin-left: 40px;
|
||||
background: var(--color-code-block);
|
||||
padding: 5px;
|
||||
margin-bottom: 0;
|
||||
display: block;
|
||||
}
|
||||
#buildCommands ul {
|
||||
list-style: '$ ';
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--color-primary);
|
||||
text-decoration-color: transparent;
|
||||
transition: text-decoration-color 200ms;
|
||||
}
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
select, button {
|
||||
border: 1px solid var(--color-select-border);
|
||||
background-color: var(--background);
|
||||
color: var(--color);
|
||||
padding: 5px;
|
||||
margin-top: 5px;
|
||||
transition: border 200ms, padding 200ms;
|
||||
border-radius: 999em;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
select:focus, button:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-primary);
|
||||
border-width: 2px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
label * {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
input[type=checkbox] {
|
||||
margin-right: 5px;
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
background-color: var(--color-checkbox-background);
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
border-radius: 3px;
|
||||
position: relative;
|
||||
transition: background-color 200ms;
|
||||
}
|
||||
input[type=checkbox]::after {
|
||||
content: "";
|
||||
transition: border-color 200ms;
|
||||
position: absolute;
|
||||
left: 6px;
|
||||
top: 2px;
|
||||
width: 5px;
|
||||
height: 10px;
|
||||
border: solid transparent;
|
||||
border-width: 0 3px 3px 0;
|
||||
-webkit-transform: rotate(45deg);
|
||||
-ms-transform: rotate(45deg);
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
input[type=checkbox]:checked {
|
||||
background-color: var(--color-primary);
|
||||
}
|
||||
input[type=checkbox]:checked::after {
|
||||
border-color: var(--color-checkbox-tick);
|
||||
}
|
||||
|
||||
input[type=radio] {
|
||||
margin-right: 5px;
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
background-color: var(--color-checkbox-background);
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
border-radius: 100%;
|
||||
position: relative;
|
||||
transition: background-color 200ms;
|
||||
}
|
||||
input[type=radio]::after {
|
||||
content: "";
|
||||
transition: border-color 200ms;
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: 10px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-radius: 100%;
|
||||
background-color: transparent;
|
||||
transition: width 200ms, height 200ms, left 200ms, top 200ms, background-color 100ms;
|
||||
}
|
||||
input[type=radio]:checked::after {
|
||||
background-color: var(--color-primary);
|
||||
left: 2px;
|
||||
top: 2px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
#copyBuildCommandsButton {
|
||||
margin-left: 40px;
|
||||
}
|
||||
#copyBuildCommandsButton.success {
|
||||
background: var(--color-copy-success-background);
|
||||
border-color: var(--color-copy-success-border);
|
||||
}
|
||||
#copyBuildCommandsButton.fail {
|
||||
background: var(--color-copy-fail-background);
|
||||
border-color: var(--color-copy-fail-border);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body onload="onLoad(true)" onpopstate="onLoad(false)">
|
||||
|
||||
<div class="main">
|
||||
<div id="osSelectDiv" class="large" style="text-align:center;">
|
||||
<p>Choose an operating system, on which you want to use the Telegram Bot API server:</p>
|
||||
<div id="osSelectDiv" class="large">
|
||||
<p>Choose an operating system on which you want to use the Telegram Bot API server:</p>
|
||||
<select id="osSelect" onchange="onOsChanged(false)" autofocus class="large">
|
||||
<option>Choose an operating system:</option>
|
||||
<option>Windows</option>
|
||||
|
|
@ -27,24 +194,26 @@ select.large { font-size: large; }
|
|||
<p></p>
|
||||
</div>
|
||||
|
||||
<div id="linuxSelectDiv" class="hide" style="text-align:center;">
|
||||
<p>Choose a Linux distro, on which you want to use the Telegram Bot API server:</p>
|
||||
<div id="linuxSelectDiv" class="hide">
|
||||
<p>Choose a Linux distro on which you want to use the Telegram Bot API server:</p>
|
||||
<select id="linuxSelect" onchange="onOsChanged(false)" class="large">
|
||||
<option>Choose a Linux distro:</option>
|
||||
<option>Alpine</option>
|
||||
<option>Debian 8</option>
|
||||
<option>Debian 9</option>
|
||||
<option>Debian 10</option>
|
||||
<option>CentOS 7</option>
|
||||
<option>CentOS 8</option>
|
||||
<option>Debian 8/9</option>
|
||||
<option>Debian 10+</option>
|
||||
<option>Ubuntu 14</option>
|
||||
<option>Ubuntu 16</option>
|
||||
<option>Ubuntu 18</option>
|
||||
<option>Ubuntu 20</option>
|
||||
<option>Ubuntu 22</option>
|
||||
<option>Other</option>
|
||||
</select>
|
||||
<p></p>
|
||||
</div>
|
||||
|
||||
<div id="buildOptionsDiv" class="hide" style="text-align:center;">
|
||||
<div id="buildOptionsDiv" class="hide">
|
||||
<div id="buildDebugDiv" class="hide">
|
||||
<label><input type="checkbox" id="buildDebugCheckbox" onchange="onOptionsChanged()"/>Build the debug binary. Debug binaries are much larger and slower than the release one.</label>
|
||||
</div>
|
||||
|
|
@ -58,7 +227,7 @@ select.large { font-size: large; }
|
|||
<div id="buildCompilerDiv" class="hide">
|
||||
<span>Choose which compiler you want to use to build the Telegram Bot API server:</span><br>
|
||||
<label><input type="radio" id="buildCompilerRadioGcc" name="buildCompilerRadio" onchange="onOptionsChanged()" checked/>g++</label>
|
||||
<label><input type="radio" id="buildCompilerRadioClang" name="buildCompilerRadio" onchange="onOptionsChanged()"/>clang++ (recommended)</label>
|
||||
<label><input type="radio" id="buildCompilerRadioClang" name="buildCompilerRadio" onchange="onOptionsChanged()"/>clang (recommended)</label>
|
||||
<p></p>
|
||||
</div>
|
||||
|
||||
|
|
@ -76,6 +245,13 @@ select.large { font-size: large; }
|
|||
<p></p>
|
||||
</div>
|
||||
|
||||
<div id="buildMacOsHostDiv" class="hide">
|
||||
<span>Choose host architecture:</span><br>
|
||||
<label><input type="radio" id="buildMacOsHostRadioAppleSilicon" name="buildMacOsHostRadio" onchange="onOptionsChanged()" checked/>Apple silicon</label>
|
||||
<label><input type="radio" id="buildMacOsHostRadioIntel" name="buildMacOsHostRadio" onchange="onOptionsChanged()"/>Intel</label>
|
||||
<p></p>
|
||||
</div>
|
||||
|
||||
<div id="buildBitnessDiv" class="hide">
|
||||
<span>Choose for which bitness you want to build the Telegram Bot API server:</span><br>
|
||||
<label><input type="radio" id="buildBitnessRadio64" name="buildBitnessRadio" onchange="onOptionsChanged()" checked/>64</label>
|
||||
|
|
@ -90,13 +266,16 @@ select.large { font-size: large; }
|
|||
<p></p>
|
||||
</div>
|
||||
|
||||
<div id="buildTextDiv" class="hide" style="text-align:center;">
|
||||
<div id="buildTextDiv" class="hide">
|
||||
<p id="buildText">Hidden text</p>
|
||||
</div>
|
||||
|
||||
<div id="buildCommandsDiv" class="hide" style="text-align:left;">
|
||||
<div id="buildCommandsDiv" class="hide">
|
||||
<p id="buildPre">Hidden text</p>
|
||||
<code id="buildCommands">Empty commands</code>
|
||||
<button id="copyBuildCommandsButton" onclick="copyBuildInstructions()">
|
||||
<span id="copyBuildCommandsText">Copy</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -119,7 +298,7 @@ function onLoad(initial) {
|
|||
function onOsChanged(initial) {
|
||||
var os = document.getElementById('osSelect').value;
|
||||
if (os.includes('Choose ')) {
|
||||
if (history.state != '') {
|
||||
if (history.state !== '' && history.state !== null) {
|
||||
history.pushState('', '', 'build.html');
|
||||
}
|
||||
|
||||
|
|
@ -174,7 +353,7 @@ function onOptionsChanged() {
|
|||
document.getElementById('buildCommandsDiv').style.display = 'block';
|
||||
|
||||
var use_clang = os_freebsd || os_openbsd;
|
||||
if (os_linux && linux_distro !== 'Alpine') {
|
||||
if (os_linux && linux_distro !== 'Alpine' && !linux_distro.includes('CentOS')) {
|
||||
document.getElementById('buildCompilerDiv').style.display = 'block';
|
||||
use_clang = document.getElementById('buildCompilerRadioClang').checked;
|
||||
} else {
|
||||
|
|
@ -192,6 +371,8 @@ function onOptionsChanged() {
|
|||
var use_powershell = false;
|
||||
var use_cmd = false;
|
||||
var use_csh = false;
|
||||
var homebrew_install_dir = '';
|
||||
var os_mac_host_name = '';
|
||||
if (os_windows) {
|
||||
document.getElementById('buildShellDiv').style.display = 'block';
|
||||
use_powershell = document.getElementById('buildShellRadioPowerShell').checked;
|
||||
|
|
@ -204,6 +385,18 @@ function onOptionsChanged() {
|
|||
} else {
|
||||
document.getElementById('buildShellBsdDiv').style.display = 'none';
|
||||
}
|
||||
if (os_mac) {
|
||||
document.getElementById('buildMacOsHostDiv').style.display = 'block';
|
||||
if (document.getElementById('buildMacOsHostRadioAppleSilicon').checked) {
|
||||
homebrew_install_dir = '/opt/homebrew';
|
||||
os_mac_host_name = 'Apple silicon';
|
||||
} else {
|
||||
homebrew_install_dir = '/usr/local';
|
||||
os_mac_host_name = 'Intel';
|
||||
}
|
||||
} else {
|
||||
document.getElementById('buildMacOsHostDiv').style.display = 'none';
|
||||
}
|
||||
|
||||
var use_msvc = os_windows;
|
||||
var use_vcpkg = os_windows;
|
||||
|
|
@ -247,10 +440,9 @@ function onOptionsChanged() {
|
|||
pre_text.push('Download and install <a href="https://visualstudio.microsoft.com/ru/vs/community/">Microsoft Visual Studio</a>. Enable C++ support while installing.');
|
||||
pre_text.push('Download and install <a href="https://cmake.org/download/">CMake</a>; choose "Add CMake to the system PATH" option while installing.');
|
||||
pre_text.push('Download and install <a href="https://git-scm.com/download/win">Git</a>.');
|
||||
pre_text.push('Download and install <a href="https://sourceforge.net/projects/gnuwin32/files/gperf/3.0.1/">gperf</a>. Add the path to gperf.exe to the PATH environment variable.');
|
||||
}
|
||||
if (os_linux && linux_distro === 'Other') {
|
||||
var compiler = use_clang ? 'clang++ >= 3.4' : 'g++ >= 4.9.2';
|
||||
var compiler = use_clang ? 'clang >= 3.4, libc++' : 'g++ >= 4.9.2';
|
||||
pre_text.push('Install Git, ' + compiler + ', make, CMake >= 3.0.2, OpenSSL-dev, zlib-dev, gperf using your package manager.');
|
||||
}
|
||||
if (os_freebsd) {
|
||||
|
|
@ -262,7 +454,7 @@ function onOptionsChanged() {
|
|||
pre_text.push('Note that building requires a lot of memory, so you may need to increase allowed per-process memory usage in /etc/login.conf or build from root.');
|
||||
}
|
||||
if (os_netbsd) {
|
||||
pre_text.push('Note that the following instruction is for NetBSD 8.0 and default SH shell.');
|
||||
pre_text.push('Note that the following instruction is for NetBSD 8+ and default SH shell.');
|
||||
}
|
||||
|
||||
var terminal_name = (function () {
|
||||
|
|
@ -302,6 +494,8 @@ function onOptionsChanged() {
|
|||
return '-6.0';
|
||||
case 'Ubuntu 20':
|
||||
return '-10';
|
||||
case 'Ubuntu 22':
|
||||
return '-14';
|
||||
default:
|
||||
return ''; // use default version
|
||||
}
|
||||
|
|
@ -309,9 +503,10 @@ function onOptionsChanged() {
|
|||
|
||||
var commands = [];
|
||||
|
||||
var cmake = 'cmake';
|
||||
if (os_mac) {
|
||||
commands.push('xcode-select --install');
|
||||
commands.push('/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"');
|
||||
commands.push('/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"');
|
||||
commands.push('brew install gperf cmake openssl');
|
||||
} else if (os_linux && linux_distro !== 'Other') {
|
||||
switch (linux_distro) {
|
||||
|
|
@ -321,13 +516,28 @@ function onOptionsChanged() {
|
|||
var packages = 'alpine-sdk linux-headers git zlib-dev openssl-dev gperf cmake';
|
||||
commands.push(sudo + 'apk add --update ' + packages);
|
||||
break;
|
||||
case 'Debian 8':
|
||||
case 'Debian 9':
|
||||
case 'Debian 10':
|
||||
case 'CentOS 7':
|
||||
case 'CentOS 8':
|
||||
commands.push(sudo + 'yum update -y');
|
||||
var packages = 'gcc-c++ make git zlib-devel openssl-devel';
|
||||
if (linux_distro === 'CentOS 7') {
|
||||
commands.push(sudo + 'yum install -y centos-release-scl-rh epel-release');
|
||||
commands.push(sudo + 'yum install -y devtoolset-9-gcc devtoolset-9-gcc-c++');
|
||||
cmake = 'cmake3';
|
||||
packages += ' gperf';
|
||||
} else {
|
||||
commands.push(sudo + 'dnf --enablerepo=powertools install gperf');
|
||||
}
|
||||
packages += ' ' + cmake;
|
||||
commands.push(sudo + 'yum install -y ' + packages);
|
||||
break;
|
||||
case 'Debian 8/9':
|
||||
case 'Debian 10+':
|
||||
case 'Ubuntu 14':
|
||||
case 'Ubuntu 16':
|
||||
case 'Ubuntu 18':
|
||||
case 'Ubuntu 20':
|
||||
case 'Ubuntu 22':
|
||||
if (linux_distro.includes('Debian') && !use_root) {
|
||||
commands.push('su -');
|
||||
}
|
||||
|
|
@ -344,7 +554,7 @@ function onOptionsChanged() {
|
|||
}
|
||||
if (use_clang) {
|
||||
packages += ' clang' + getClangVersionSuffix() + ' libc++-dev';
|
||||
if (linux_distro === 'Debian 10' || linux_distro === 'Ubuntu 18' || linux_distro === 'Ubuntu 20') {
|
||||
if (linux_distro === 'Debian 10+' || linux_distro === 'Ubuntu 18' || linux_distro === 'Ubuntu 20' || linux_distro === 'Ubuntu 22') {
|
||||
packages += ' libc++abi-dev';
|
||||
}
|
||||
} else {
|
||||
|
|
@ -367,7 +577,7 @@ function onOptionsChanged() {
|
|||
if (!use_root) {
|
||||
commands.push('su -');
|
||||
}
|
||||
var packages = 'git gperf cmake';
|
||||
var packages = 'git gperf php-7.2.10 cmake';
|
||||
commands.push('pkg_add -z ' + packages);
|
||||
if (!use_root) {
|
||||
commands.push('exit');
|
||||
|
|
@ -376,8 +586,8 @@ function onOptionsChanged() {
|
|||
if (!use_root) {
|
||||
commands.push('su -');
|
||||
}
|
||||
commands.push('export PKG_PATH=ftp://ftp.netbsd.org/pub/pkgsrc/packages/NetBSD/i386/8.0_2019Q2/All');
|
||||
var packages = 'git gperf cmake openssl gcc5-libs';
|
||||
commands.push('export PKG_PATH=http://cdn.netbsd.org/pub/pkgsrc/packages/NetBSD/$(uname -p)/$(uname -r)/All');
|
||||
var packages = 'git gperf cmake openssl gcc12-libs mozilla-rootcerts-openssl';
|
||||
commands.push('pkg_add ' + packages);
|
||||
if (!use_root) {
|
||||
commands.push('exit');
|
||||
|
|
@ -392,9 +602,9 @@ function onOptionsChanged() {
|
|||
commands.push('cd vcpkg');
|
||||
commands.push(local + 'bootstrap-vcpkg.bat');
|
||||
if (build_64bit) {
|
||||
commands.push(local + 'vcpkg.exe install openssl:x64-windows zlib:x64-windows');
|
||||
commands.push(local + 'vcpkg.exe install gperf:x64-windows openssl:x64-windows zlib:x64-windows');
|
||||
} else {
|
||||
commands.push(local + 'vcpkg.exe install openssl:x86-windows zlib:x86-windows');
|
||||
commands.push(local + 'vcpkg.exe install gperf:x86-windows openssl:x86-windows zlib:x86-windows');
|
||||
}
|
||||
commands.push('cd ..');
|
||||
}
|
||||
|
|
@ -418,37 +628,9 @@ function onOptionsChanged() {
|
|||
commands.push('mkdir build');
|
||||
commands.push('cd build');
|
||||
|
||||
if (!use_msvc) {
|
||||
var c_flags = [];
|
||||
var cxx_flags = [];
|
||||
|
||||
if (build_32bit) {
|
||||
c_flags.push('-m32');
|
||||
cxx_flags.push('-m32');
|
||||
} else if (build_64bit) {
|
||||
c_flags.push('-m64');
|
||||
cxx_flags.push('-m64');
|
||||
}
|
||||
|
||||
if (os_linux) {
|
||||
if (use_clang) {
|
||||
cxx_flags.push('-stdlib=libc++');
|
||||
} else {
|
||||
cxx_flags.push('');
|
||||
}
|
||||
}
|
||||
|
||||
if (c_flags.length) {
|
||||
commands.push('export CFLAGS="' + c_flags.join(' ') + '"');
|
||||
}
|
||||
if (cxx_flags.length) {
|
||||
commands.push('export CXXFLAGS="' + cxx_flags.join(' ') + '"');
|
||||
}
|
||||
}
|
||||
|
||||
cmake_init_options = getBacicCmakeInitOptions();
|
||||
if (os_mac) {
|
||||
cmake_init_options.push('-DOPENSSL_ROOT_DIR=/usr/local/opt/openssl/');
|
||||
cmake_init_options.push('-DOPENSSL_ROOT_DIR=' + homebrew_install_dir + '/opt/openssl/');
|
||||
}
|
||||
cmake_init_options.push('-DCMAKE_INSTALL_PREFIX:PATH=' + install_dir);
|
||||
if (use_vcpkg) {
|
||||
|
|
@ -460,16 +642,24 @@ function onOptionsChanged() {
|
|||
if (os_linux) {
|
||||
if (use_clang) {
|
||||
var clang_version_suffix = getClangVersionSuffix();
|
||||
prefix = 'CC=/usr/bin/clang' + clang_version_suffix + ' CXX=/usr/bin/clang++' + clang_version_suffix + ' ';
|
||||
prefix = 'CXXFLAGS="-stdlib=libc++" CC=/usr/bin/clang' + clang_version_suffix + ' CXX=/usr/bin/clang++' + clang_version_suffix + ' ';
|
||||
} else if (linux_distro === 'Ubuntu 14') {
|
||||
prefix = 'CC=/usr/bin/gcc-4.9 CXX=/usr/bin/g++-4.9 ';
|
||||
} else if (linux_distro === 'CentOS 7') {
|
||||
prefix = 'CC=/opt/rh/devtoolset-9/root/usr/bin/gcc CXX=/opt/rh/devtoolset-9/root/usr/bin/g++ ';
|
||||
}
|
||||
}
|
||||
return prefix + 'cmake ' + options.join(' ') + ' ..';
|
||||
return prefix + cmake + ' ' + options.join(' ') + ' ..';
|
||||
}
|
||||
commands.push(getCmakeInitCommand(cmake_init_options));
|
||||
|
||||
let build_command = 'cmake --build . --target install';
|
||||
if (os_openbsd) {
|
||||
commands.push(cmake + ' --build . --target prepare_cross_compiling');
|
||||
commands.push('cd ../td');
|
||||
commands.push('php-7.2 SplitSource.php');
|
||||
commands.push('cd ../build');
|
||||
}
|
||||
let build_command = cmake + ' --build . --target install';
|
||||
if (use_msvc) {
|
||||
if (!is_debug_build) {
|
||||
commands.push(build_command + ' --config Release');
|
||||
|
|
@ -480,12 +670,36 @@ function onOptionsChanged() {
|
|||
} else {
|
||||
commands.push(build_command);
|
||||
}
|
||||
if (os_openbsd) {
|
||||
commands.push('cd ../td');
|
||||
commands.push('php-7.2 SplitSource.php --undo');
|
||||
}
|
||||
commands.push('cd ../..');
|
||||
if (install_dir !== '/usr/local') {
|
||||
install_dir = 'telegram-bot-api';
|
||||
}
|
||||
commands.push((use_powershell ? 'dir ' : 'ls -l ') + install_dir + '/bin/telegram-bot-api*');
|
||||
document.getElementById('buildCommands').innerHTML = '<ul><li>' + commands.join('</li><li>') + '</li></ul>';
|
||||
document.getElementById('copyBuildCommandsButton').style.display = commands.includes('exit') ? 'none' : 'block';
|
||||
}
|
||||
|
||||
function copyBuildInstructions() {
|
||||
var text = document.getElementById('buildCommands').innerText;
|
||||
|
||||
function resetButtonState (state) {
|
||||
document.getElementById('copyBuildCommandsButton').classList.remove(state);
|
||||
document.getElementById('copyBuildCommandsText').innerText = "Copy";
|
||||
}
|
||||
|
||||
navigator.clipboard.writeText(text).then(result => {
|
||||
document.getElementById('copyBuildCommandsButton').classList.add('success');
|
||||
document.getElementById('copyBuildCommandsText').innerText = "Copied!";
|
||||
setTimeout(() => resetButtonState('success'), 5000);
|
||||
}, error => {
|
||||
document.getElementById('copyBuildCommandsButton').classList.add('fail');
|
||||
document.getElementById('copyBuildCommandsText').innerText = "Couldn't copy :(";
|
||||
setTimeout(() => resetButtonState('fail'), 5000);
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
|
|
|||
2
td
2
td
|
|
@ -1 +1 @@
|
|||
Subproject commit c9a70fcd49757084a0c3124e610e92bf0586894b
|
||||
Subproject commit d93a99e3351db82573d765ce4f5e84714c277518
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,16 +1,16 @@
|
|||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#include "telegram-bot-api/ClientManager.h"
|
||||
|
||||
#include "telegram-bot-api/Client.h"
|
||||
#include "telegram-bot-api/ClientParameters.h"
|
||||
#include "telegram-bot-api/WebhookActor.h"
|
||||
|
||||
#include "td/telegram/ClientActor.h"
|
||||
#include "td/telegram/td_api.h"
|
||||
|
||||
#include "td/db/binlog/Binlog.h"
|
||||
#include "td/db/binlog/ConcurrentBinlog.h"
|
||||
|
|
@ -29,12 +29,17 @@
|
|||
#include "td/utils/Parser.h"
|
||||
#include "td/utils/port/IPAddress.h"
|
||||
#include "td/utils/port/Stat.h"
|
||||
#include "td/utils/port/thread.h"
|
||||
#include "td/utils/Slice.h"
|
||||
#include "td/utils/SliceBuilder.h"
|
||||
#include "td/utils/StackAllocator.h"
|
||||
#include "td/utils/StringBuilder.h"
|
||||
#include "td/utils/Time.h"
|
||||
|
||||
#include <map>
|
||||
#include "memprof/memprof.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <tuple>
|
||||
|
||||
namespace telegram_bot_api {
|
||||
|
|
@ -46,6 +51,8 @@ void ClientManager::close(td::Promise<td::Unit> &&promise) {
|
|||
}
|
||||
|
||||
close_flag_ = true;
|
||||
watchdog_id_.reset();
|
||||
dump_statistics();
|
||||
auto ids = clients_.ids();
|
||||
for (auto id : ids) {
|
||||
auto *client_info = clients_.get(id);
|
||||
|
|
@ -69,8 +76,12 @@ void ClientManager::send(PromisedQueryPtr query) {
|
|||
return fail_query(401, "Unauthorized: invalid token specified", std::move(query));
|
||||
}
|
||||
auto r_user_id = td::to_integer_safe<td::int64>(query->token().substr(0, token.find(':')));
|
||||
if (r_user_id.is_error() || r_user_id.ok() < 0 || !token_range_(r_user_id.ok())) {
|
||||
return fail_query(401, "Unauthorized: unallowed token specified", std::move(query));
|
||||
if (r_user_id.is_error() || !token_range_(r_user_id.ok())) {
|
||||
return fail_query(421, "Misdirected Request: unallowed token specified", std::move(query));
|
||||
}
|
||||
auto user_id = r_user_id.ok();
|
||||
if (user_id <= 0 || user_id >= (static_cast<td::int64>(1) << 54)) {
|
||||
return fail_query(401, "Unauthorized: invalid token specified", std::move(query));
|
||||
}
|
||||
|
||||
if (query->is_test_dc()) {
|
||||
|
|
@ -79,13 +90,12 @@ void ClientManager::send(PromisedQueryPtr query) {
|
|||
|
||||
auto id_it = token_to_id_.find(token);
|
||||
if (id_it == token_to_id_.end()) {
|
||||
std::string ip_address;
|
||||
if (query->peer_address().is_valid() && !query->peer_address().is_reserved()) { // external connection
|
||||
ip_address = query->peer_address().get_ip_str().str();
|
||||
} else {
|
||||
// invalid peer address or connection from the local network
|
||||
ip_address = query->get_header("x-real-ip").str();
|
||||
auto method = query->method();
|
||||
if (method == "close") {
|
||||
return fail_query(400, "Bad Request: the bot has already been closed", std::move(query));
|
||||
}
|
||||
|
||||
td::string ip_address = query->get_peer_ip_address();
|
||||
if (!ip_address.empty()) {
|
||||
td::IPAddress tmp;
|
||||
tmp.init_host_port(ip_address, 0).ignore();
|
||||
|
|
@ -94,7 +104,7 @@ void ClientManager::send(PromisedQueryPtr query) {
|
|||
ip_address = tmp.get_ip_str().str();
|
||||
}
|
||||
}
|
||||
LOG(DEBUG) << "Receive incoming query for new bot " << token << " from " << query->peer_address();
|
||||
LOG(DEBUG) << "Receive incoming query for new bot " << token << " from " << ip_address;
|
||||
if (!ip_address.empty()) {
|
||||
LOG(DEBUG) << "Check Client creation flood control for IP address " << ip_address;
|
||||
auto res = flood_controls_.emplace(std::move(ip_address), td::FloodControlFast());
|
||||
|
|
@ -103,44 +113,75 @@ void ClientManager::send(PromisedQueryPtr query) {
|
|||
flood_control.add_limit(60, 20); // 20 in a minute
|
||||
flood_control.add_limit(60 * 60, 600); // 600 in an hour
|
||||
}
|
||||
td::uint32 now = static_cast<td::uint32>(td::Time::now());
|
||||
td::uint32 wakeup_at = flood_control.get_wakeup_at();
|
||||
auto now = td::Time::now();
|
||||
auto wakeup_at = flood_control.get_wakeup_at();
|
||||
if (wakeup_at > now) {
|
||||
LOG(INFO) << "Failed to create Client from IP address " << ip_address;
|
||||
return query->set_retry_after_error(static_cast<int>(wakeup_at - now) + 1);
|
||||
}
|
||||
flood_control.add_event(static_cast<td::int32>(now));
|
||||
flood_control.add_event(now);
|
||||
}
|
||||
auto tqueue_id = get_tqueue_id(user_id, query->is_test_dc());
|
||||
if (active_client_count_.count(tqueue_id) != 0) {
|
||||
// return query->set_retry_after_error(1);
|
||||
}
|
||||
|
||||
auto id = clients_.create(ClientInfo{BotStatActor(stat_.actor_id(&stat_)), token, td::ActorOwn<Client>()});
|
||||
auto id =
|
||||
clients_.create(ClientInfo{BotStatActor(stat_.actor_id(&stat_)), token, tqueue_id, td::ActorOwn<Client>()});
|
||||
auto *client_info = clients_.get(id);
|
||||
auto stat_actor = client_info->stat_.actor_id(&client_info->stat_);
|
||||
auto client_id = td::create_actor<Client>(
|
||||
PSLICE() << "Client/" << token, actor_shared(this, id), query->token().str(), query->is_test_dc(),
|
||||
get_tqueue_id(r_user_id.ok(), query->is_test_dc()), parameters_, std::move(stat_actor));
|
||||
client_info->client_ = td::create_actor<Client>(PSLICE() << "Client/" << token, actor_shared(this, id),
|
||||
query->token().str(), query->is_test_dc(), tqueue_id, parameters_,
|
||||
client_info->stat_.actor_id(&client_info->stat_));
|
||||
|
||||
auto method = query->method();
|
||||
if (method != "deletewebhook" && method != "setwebhook") {
|
||||
auto bot_token_with_dc = PSTRING() << query->token() << (query->is_test_dc() ? ":T" : "");
|
||||
auto webhook_info = parameters_->shared_data_->webhook_db_->get(bot_token_with_dc);
|
||||
if (!webhook_info.empty()) {
|
||||
send_closure(client_id, &Client::send,
|
||||
send_closure(client_info->client_, &Client::send,
|
||||
get_webhook_restore_query(bot_token_with_dc, webhook_info, parameters_->shared_data_));
|
||||
}
|
||||
}
|
||||
|
||||
clients_.get(id)->client_ = std::move(client_id);
|
||||
std::tie(id_it, std::ignore) = token_to_id_.emplace(token, id);
|
||||
}
|
||||
auto *client_info = clients_.get(id_it->second);
|
||||
|
||||
if (!query->is_internal()) {
|
||||
query->set_stat_actor(client_info->stat_.actor_id(&client_info->stat_));
|
||||
}
|
||||
send_closure(client_info->client_, &Client::send, std::move(query)); // will send 429 if the client is already closed
|
||||
send_closure(clients_.get(id_it->second)->client_, &Client::send,
|
||||
std::move(query)); // will send 429 if the client is already closed
|
||||
}
|
||||
|
||||
void ClientManager::get_stats(td::PromiseActor<td::BufferSlice> promise,
|
||||
ClientManager::TopClients ClientManager::get_top_clients(std::size_t max_count, td::Slice token_filter) {
|
||||
auto now = td::Time::now();
|
||||
TopClients result;
|
||||
td::vector<std::pair<td::int64, td::uint64>> top_client_ids;
|
||||
for (auto id : clients_.ids()) {
|
||||
auto *client_info = clients_.get(id);
|
||||
CHECK(client_info);
|
||||
|
||||
if (client_info->stat_.is_active(now)) {
|
||||
result.active_count++;
|
||||
}
|
||||
|
||||
if (!td::begins_with(client_info->token_, token_filter)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto score = static_cast<td::int64>(client_info->stat_.get_score(now) * -1e9);
|
||||
if (score == 0 && top_client_ids.size() >= max_count) {
|
||||
continue;
|
||||
}
|
||||
top_client_ids.emplace_back(score, id);
|
||||
}
|
||||
if (top_client_ids.size() < max_count) {
|
||||
max_count = top_client_ids.size();
|
||||
}
|
||||
std::partial_sort(top_client_ids.begin(), top_client_ids.begin() + max_count, top_client_ids.end());
|
||||
result.top_client_ids.reserve(max_count);
|
||||
for (std::size_t i = 0; i < max_count; i++) {
|
||||
result.top_client_ids.push_back(top_client_ids[i].second);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void ClientManager::get_stats(td::Promise<td::BufferSlice> promise,
|
||||
td::vector<std::pair<td::string, td::string>> args) {
|
||||
if (close_flag_) {
|
||||
promise.set_value(td::BufferSlice("Closing"));
|
||||
|
|
@ -151,96 +192,104 @@ void ClientManager::get_stats(td::PromiseActor<td::BufferSlice> promise,
|
|||
td::StringBuilder sb(buf.as_slice());
|
||||
|
||||
td::Slice id_filter;
|
||||
int new_verbosity_level = -1;
|
||||
td::string tag;
|
||||
for (auto &arg : args) {
|
||||
if (arg.first == "id") {
|
||||
id_filter = arg.second;
|
||||
}
|
||||
if (arg.first == "v") {
|
||||
auto r_verbosity = td::to_integer_safe<int>(arg.second);
|
||||
if (r_verbosity.is_ok()) {
|
||||
parameters_->shared_data_->next_verbosity_level_ = r_verbosity.ok();
|
||||
auto r_new_verbosity_level = td::to_integer_safe<int>(arg.second);
|
||||
if (r_new_verbosity_level.is_ok()) {
|
||||
new_verbosity_level = r_new_verbosity_level.ok();
|
||||
}
|
||||
}
|
||||
if (arg.first == "tag") {
|
||||
tag = arg.second;
|
||||
}
|
||||
}
|
||||
if (new_verbosity_level > 0) {
|
||||
if (tag.empty()) {
|
||||
parameters_->shared_data_->next_verbosity_level_ = new_verbosity_level;
|
||||
} else {
|
||||
td::ClientActor::execute(td::td_api::make_object<td::td_api::setLogTagVerbosityLevel>(tag, new_verbosity_level));
|
||||
}
|
||||
}
|
||||
|
||||
auto now = td::Time::now();
|
||||
td::int32 active_bot_count = 0;
|
||||
std::multimap<td::int64, td::uint64> top_bot_ids;
|
||||
for (auto id : clients_.ids()) {
|
||||
auto *client_info = clients_.get(id);
|
||||
CHECK(client_info);
|
||||
|
||||
if (client_info->stat_.is_active(now)) {
|
||||
active_bot_count++;
|
||||
}
|
||||
|
||||
if (!td::begins_with(client_info->token_, id_filter)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto stats = client_info->stat_.as_vector(now);
|
||||
double score = 0.0;
|
||||
for (auto &stat : stats) {
|
||||
if (stat.key_ == "update_count" || stat.key_ == "request_count") {
|
||||
score -= td::to_double(stat.value_);
|
||||
}
|
||||
}
|
||||
top_bot_ids.emplace(static_cast<td::int64>(score * 1e9), id);
|
||||
}
|
||||
|
||||
sb << stat_.get_description() << "\n";
|
||||
auto top_clients = get_top_clients(50, id_filter);
|
||||
sb << BotStatActor::get_description() << '\n';
|
||||
if (id_filter.empty()) {
|
||||
sb << "uptime\t" << now - parameters_->start_timestamp_ << "\n";
|
||||
sb << "bot_count\t" << clients_.size() << "\n";
|
||||
sb << "active_bot_count\t" << active_bot_count << "\n";
|
||||
sb << "uptime\t" << now - parameters_->start_time_ << '\n';
|
||||
sb << "bot_count\t" << clients_.size() << '\n';
|
||||
sb << "active_bot_count\t" << top_clients.active_count << '\n';
|
||||
auto r_mem_stat = td::mem_stat();
|
||||
if (r_mem_stat.is_ok()) {
|
||||
auto mem_stat = r_mem_stat.move_as_ok();
|
||||
sb << "rss\t" << td::format::as_size(mem_stat.resident_size_) << "\n";
|
||||
sb << "vm\t" << td::format::as_size(mem_stat.virtual_size_) << "\n";
|
||||
sb << "rss_peak\t" << td::format::as_size(mem_stat.resident_size_peak_) << "\n";
|
||||
sb << "vm_peak\t" << td::format::as_size(mem_stat.virtual_size_peak_) << "\n";
|
||||
sb << "rss\t" << td::format::as_size(mem_stat.resident_size_) << '\n';
|
||||
sb << "vm\t" << td::format::as_size(mem_stat.virtual_size_) << '\n';
|
||||
sb << "rss_peak\t" << td::format::as_size(mem_stat.resident_size_peak_) << '\n';
|
||||
sb << "vm_peak\t" << td::format::as_size(mem_stat.virtual_size_peak_) << '\n';
|
||||
} else {
|
||||
LOG(INFO) << "Failed to get memory statistics: " << r_mem_stat.error();
|
||||
}
|
||||
|
||||
ServerCpuStat::update(td::Time::now());
|
||||
auto cpu_stats = ServerCpuStat::instance().as_vector(td::Time::now());
|
||||
for (auto &stat : cpu_stats) {
|
||||
sb << stat.key_ << "\t" << stat.value_ << "\n";
|
||||
sb << stat.key_ << "\t" << stat.value_ << '\n';
|
||||
}
|
||||
|
||||
sb << "buffer_memory\t" << td::format::as_size(td::BufferAllocator::get_buffer_mem()) << "\n";
|
||||
sb << "active_webhook_connections\t" << WebhookActor::get_total_connections_count() << "\n";
|
||||
sb << "active_requests\t" << parameters_->shared_data_->query_count_.load() << "\n";
|
||||
sb << "active_network_queries\t" << td::get_pending_network_query_count(*parameters_->net_query_stats_) << "\n";
|
||||
sb << "buffer_memory\t" << td::format::as_size(td::BufferAllocator::get_buffer_mem()) << '\n';
|
||||
sb << "active_webhook_connections\t" << WebhookActor::get_total_connection_count() << '\n';
|
||||
sb << "active_requests\t" << parameters_->shared_data_->query_count_.load(std::memory_order_relaxed) << '\n';
|
||||
sb << "active_network_queries\t" << td::get_pending_network_query_count(*parameters_->net_query_stats_) << '\n';
|
||||
auto stats = stat_.as_vector(now);
|
||||
for (auto &stat : stats) {
|
||||
sb << stat.key_ << "\t" << stat.value_ << "\n";
|
||||
sb << stat.key_ << "\t" << stat.value_ << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
for (auto top_bot_id : top_bot_ids) {
|
||||
auto *client_info = clients_.get(top_bot_id.second);
|
||||
for (auto top_client_id : top_clients.top_client_ids) {
|
||||
auto *client_info = clients_.get(top_client_id);
|
||||
CHECK(client_info);
|
||||
|
||||
auto bot_info = client_info->client_->get_actor_unsafe()->get_bot_info();
|
||||
sb << "\n";
|
||||
sb << "id\t" << bot_info.id_ << "\n";
|
||||
sb << "uptime\t" << now - bot_info.start_timestamp_ << "\n";
|
||||
sb << "token\t" << bot_info.token_ << "\n";
|
||||
sb << "username\t" << bot_info.username_ << "\n";
|
||||
sb << "webhook\t" << bot_info.webhook_ << "\n";
|
||||
sb << "has_custom_certificate\t" << bot_info.has_webhook_certificate_ << "\n";
|
||||
sb << "head_update_id\t" << bot_info.head_update_id_ << "\n";
|
||||
sb << "tail_update_id\t" << bot_info.tail_update_id_ << "\n";
|
||||
sb << "pending_update_count\t" << bot_info.pending_update_count_ << "\n";
|
||||
sb << "webhook_max_connections\t" << bot_info.webhook_max_connections_ << "\n";
|
||||
auto bot_info = client_info->client_.get_actor_unsafe()->get_bot_info();
|
||||
auto active_request_count = client_info->stat_.get_active_request_count();
|
||||
auto active_file_upload_bytes = client_info->stat_.get_active_file_upload_bytes();
|
||||
auto active_file_upload_count = client_info->stat_.get_active_file_upload_count();
|
||||
sb << '\n';
|
||||
sb << "id\t" << bot_info.id_ << '\n';
|
||||
sb << "uptime\t" << now - bot_info.start_time_ << '\n';
|
||||
sb << "token\t" << bot_info.token_ << '\n';
|
||||
sb << "username\t" << bot_info.username_ << '\n';
|
||||
if (active_request_count != 0) {
|
||||
sb << "active_request_count\t" << active_request_count << '\n';
|
||||
}
|
||||
if (active_file_upload_bytes != 0) {
|
||||
sb << "active_file_upload_bytes\t" << active_file_upload_bytes << '\n';
|
||||
}
|
||||
if (active_file_upload_count != 0) {
|
||||
sb << "active_file_upload_count\t" << active_file_upload_count << '\n';
|
||||
}
|
||||
if (!bot_info.webhook_.empty()) {
|
||||
sb << "webhook\t" << bot_info.webhook_ << '\n';
|
||||
if (bot_info.has_webhook_certificate_) {
|
||||
sb << "has_custom_certificate\t" << bot_info.has_webhook_certificate_ << '\n';
|
||||
}
|
||||
if (bot_info.webhook_max_connections_ != parameters_->default_max_webhook_connections_) {
|
||||
sb << "webhook_max_connections\t" << bot_info.webhook_max_connections_ << '\n';
|
||||
}
|
||||
}
|
||||
sb << "head_update_id\t" << bot_info.head_update_id_ << '\n';
|
||||
if (bot_info.pending_update_count_ != 0) {
|
||||
sb << "tail_update_id\t" << bot_info.tail_update_id_ << '\n';
|
||||
sb << "pending_update_count\t" << bot_info.pending_update_count_ << '\n';
|
||||
}
|
||||
|
||||
auto stats = client_info->stat_.as_vector(now);
|
||||
for (auto &stat : stats) {
|
||||
if (stat.key_ == "update_count" || stat.key_ == "request_count") {
|
||||
sb << stat.key_ << "/sec\t" << stat.value_ << "\n";
|
||||
sb << stat.key_ << "/sec\t" << stat.value_ << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -257,20 +306,16 @@ td::int64 ClientManager::get_tqueue_id(td::int64 user_id, bool is_test_dc) {
|
|||
}
|
||||
|
||||
void ClientManager::start_up() {
|
||||
//NB: the same scheduler as for database in Td
|
||||
auto current_scheduler_id = td::Scheduler::instance()->sched_id();
|
||||
auto scheduler_count = td::Scheduler::instance()->sched_count();
|
||||
auto scheduler_id = td::min(current_scheduler_id + 1, scheduler_count - 1);
|
||||
|
||||
// init tqueue
|
||||
{
|
||||
auto load_start_time = td::Time::now();
|
||||
auto tqueue_binlog = td::make_unique<td::TQueueBinlog<td::Binlog>>();
|
||||
auto binlog = td::make_unique<td::Binlog>();
|
||||
auto tqueue = td::TQueue::create();
|
||||
td::vector<td::uint64> failed_to_replay_log_event_ids;
|
||||
td::int64 loaded_event_count = 0;
|
||||
binlog
|
||||
->init("tqueue.binlog",
|
||||
->init(parameters_->working_directory_ + "tqueue.binlog",
|
||||
[&](const td::BinlogEvent &event) {
|
||||
if (tqueue_binlog->replay(event, *tqueue).is_error()) {
|
||||
failed_to_replay_log_event_ids.push_back(event.id_);
|
||||
|
|
@ -280,7 +325,6 @@ void ClientManager::start_up() {
|
|||
})
|
||||
.ensure();
|
||||
tqueue_binlog.reset();
|
||||
LOG(WARNING) << "Loaded " << loaded_event_count << " TQueue events";
|
||||
|
||||
if (!failed_to_replay_log_event_ids.empty()) {
|
||||
LOG(ERROR) << "Failed to replay " << failed_to_replay_log_event_ids.size() << " TQueue events";
|
||||
|
|
@ -289,22 +333,28 @@ void ClientManager::start_up() {
|
|||
}
|
||||
}
|
||||
|
||||
auto concurrent_binlog = std::make_shared<td::ConcurrentBinlog>(std::move(binlog), scheduler_id);
|
||||
auto concurrent_binlog =
|
||||
std::make_shared<td::ConcurrentBinlog>(std::move(binlog), SharedData::get_binlog_scheduler_id());
|
||||
auto concurrent_tqueue_binlog = td::make_unique<td::TQueueBinlog<td::BinlogInterface>>();
|
||||
concurrent_tqueue_binlog->set_binlog(std::move(concurrent_binlog));
|
||||
tqueue->set_callback(std::move(concurrent_tqueue_binlog));
|
||||
|
||||
parameters_->shared_data_->tqueue_ = std::move(tqueue);
|
||||
|
||||
LOG(WARNING) << "Loaded " << loaded_event_count << " TQueue events in " << (td::Time::now() - load_start_time)
|
||||
<< " seconds";
|
||||
next_tqueue_gc_time_ = td::Time::now() + 600;
|
||||
}
|
||||
|
||||
// init webhook_db
|
||||
auto concurrent_webhook_db = td::make_unique<td::BinlogKeyValue<td::ConcurrentBinlog>>();
|
||||
auto status = concurrent_webhook_db->init("webhooks_db.binlog", td::DbKey::empty(), scheduler_id);
|
||||
LOG_IF(FATAL, status.is_error()) << "Can't open webhooks_db.binlog " << status.error();
|
||||
auto status = concurrent_webhook_db->init(parameters_->working_directory_ + "webhooks_db.binlog", td::DbKey::empty(),
|
||||
SharedData::get_binlog_scheduler_id());
|
||||
LOG_IF(FATAL, status.is_error()) << "Can't open webhooks_db.binlog " << status;
|
||||
parameters_->shared_data_->webhook_db_ = std::move(concurrent_webhook_db);
|
||||
|
||||
auto &webhook_db = *parameters_->shared_data_->webhook_db_;
|
||||
for (auto key_value : webhook_db.get_all()) {
|
||||
for (const auto &key_value : webhook_db.get_all()) {
|
||||
if (!token_range_(td::to_integer<td::uint64>(key_value.first))) {
|
||||
LOG(WARNING) << "DROP WEBHOOK: " << key_value.first << " ---> " << key_value.second;
|
||||
webhook_db.erase(key_value.first);
|
||||
|
|
@ -314,6 +364,11 @@ void ClientManager::start_up() {
|
|||
auto query = get_webhook_restore_query(key_value.first, key_value.second, parameters_->shared_data_);
|
||||
send_closure_later(actor_id(this), &ClientManager::send, std::move(query));
|
||||
}
|
||||
|
||||
// launch watchdog
|
||||
watchdog_id_ = td::create_actor_on_scheduler<Watchdog>("ManagerWatchdog", SharedData::get_watchdog_scheduler_id(),
|
||||
td::this_thread::get_id(), WATCHDOG_TIMEOUT);
|
||||
set_timeout_in(600.0);
|
||||
}
|
||||
|
||||
PromisedQueryPtr ClientManager::get_webhook_restore_query(td::Slice token, td::Slice webhook_info,
|
||||
|
|
@ -322,7 +377,7 @@ PromisedQueryPtr ClientManager::get_webhook_restore_query(td::Slice token, td::S
|
|||
td::vector<td::BufferSlice> containers;
|
||||
auto add_string = [&containers](td::Slice str) {
|
||||
containers.emplace_back(str);
|
||||
return containers.back().as_slice();
|
||||
return containers.back().as_mutable_slice();
|
||||
};
|
||||
|
||||
token = add_string(token);
|
||||
|
|
@ -356,6 +411,11 @@ PromisedQueryPtr ClientManager::get_webhook_restore_query(td::Slice token, td::S
|
|||
parser.skip('/');
|
||||
}
|
||||
|
||||
if (parser.try_skip("#secret")) {
|
||||
args.emplace_back(add_string("secret_token"), add_string(parser.read_till('/')));
|
||||
parser.skip('/');
|
||||
}
|
||||
|
||||
if (parser.try_skip("#allow")) {
|
||||
args.emplace_back(add_string("allowed_updates"), add_string(parser.read_till('/')));
|
||||
parser.skip('/');
|
||||
|
|
@ -364,11 +424,134 @@ PromisedQueryPtr ClientManager::get_webhook_restore_query(td::Slice token, td::S
|
|||
args.emplace_back(add_string("url"), add_string(parser.read_all()));
|
||||
|
||||
const auto method = add_string("setwebhook");
|
||||
auto query = std::make_unique<Query>(std::move(containers), token, is_test_dc, method, std::move(args),
|
||||
td::vector<std::pair<td::MutableSlice, td::MutableSlice>>(),
|
||||
td::vector<td::HttpFile>(), std::move(shared_data), td::IPAddress());
|
||||
query->set_internal(true);
|
||||
return PromisedQueryPtr(query.release(), PromiseDeleter(td::PromiseActor<td::unique_ptr<Query>>()));
|
||||
auto query = td::make_unique<Query>(std::move(containers), token, is_test_dc, method, std::move(args),
|
||||
td::vector<std::pair<td::MutableSlice, td::MutableSlice>>(),
|
||||
td::vector<td::HttpFile>(), std::move(shared_data), td::IPAddress(), true);
|
||||
return PromisedQueryPtr(query.release(), PromiseDeleter(td::Promise<td::unique_ptr<Query>>()));
|
||||
}
|
||||
|
||||
void ClientManager::dump_statistics() {
|
||||
if (is_memprof_on()) {
|
||||
LOG(WARNING) << "Memory dump:";
|
||||
td::vector<AllocInfo> v;
|
||||
dump_alloc([&](const AllocInfo &info) { v.push_back(info); });
|
||||
std::sort(v.begin(), v.end(), [](const AllocInfo &a, const AllocInfo &b) { return a.size > b.size; });
|
||||
size_t total_size = 0;
|
||||
size_t other_size = 0;
|
||||
int count = 0;
|
||||
for (auto &info : v) {
|
||||
if (count++ < 50) {
|
||||
LOG(WARNING) << td::format::as_size(info.size) << td::format::as_array(info.backtrace);
|
||||
} else {
|
||||
other_size += info.size;
|
||||
}
|
||||
total_size += info.size;
|
||||
}
|
||||
LOG(WARNING) << td::tag("other", td::format::as_size(other_size));
|
||||
LOG(WARNING) << td::tag("total size", td::format::as_size(total_size));
|
||||
LOG(WARNING) << td::tag("total traces", get_ht_size());
|
||||
LOG(WARNING) << td::tag("fast_backtrace_success_rate", get_fast_backtrace_success_rate());
|
||||
}
|
||||
auto r_mem_stat = td::mem_stat();
|
||||
if (r_mem_stat.is_ok()) {
|
||||
auto mem_stat = r_mem_stat.move_as_ok();
|
||||
LOG(WARNING) << td::tag("rss", td::format::as_size(mem_stat.resident_size_));
|
||||
LOG(WARNING) << td::tag("vm", td::format::as_size(mem_stat.virtual_size_));
|
||||
LOG(WARNING) << td::tag("rss_peak", td::format::as_size(mem_stat.resident_size_peak_));
|
||||
LOG(WARNING) << td::tag("vm_peak", td::format::as_size(mem_stat.virtual_size_peak_));
|
||||
}
|
||||
LOG(WARNING) << td::tag("buffer_mem", td::format::as_size(td::BufferAllocator::get_buffer_mem()));
|
||||
LOG(WARNING) << td::tag("buffer_slice_size", td::format::as_size(td::BufferAllocator::get_buffer_slice_size()));
|
||||
|
||||
const auto &shared_data = parameters_->shared_data_;
|
||||
auto query_list_size = shared_data->query_list_size_.load(std::memory_order_relaxed);
|
||||
auto query_count = shared_data->query_count_.load(std::memory_order_relaxed);
|
||||
LOG(WARNING) << td::tag("pending queries", query_count) << td::tag("pending requests", query_list_size);
|
||||
|
||||
td::uint64 i = 0;
|
||||
bool was_gap = false;
|
||||
for (auto end = &shared_data->query_list_, cur = end->prev; cur != end; cur = cur->prev, i++) {
|
||||
if (i < 20 || i > query_list_size - 20 || i % (query_list_size / 50 + 1) == 0) {
|
||||
if (was_gap) {
|
||||
LOG(WARNING) << "...";
|
||||
was_gap = false;
|
||||
}
|
||||
LOG(WARNING) << static_cast<const Query &>(*cur);
|
||||
} else {
|
||||
was_gap = true;
|
||||
}
|
||||
}
|
||||
|
||||
td::dump_pending_network_queries(*parameters_->net_query_stats_);
|
||||
|
||||
auto now = td::Time::now();
|
||||
auto top_clients = get_top_clients(10, {});
|
||||
for (auto top_client_id : top_clients.top_client_ids) {
|
||||
auto *client_info = clients_.get(top_client_id);
|
||||
CHECK(client_info);
|
||||
|
||||
auto bot_info = client_info->client_.get_actor_unsafe()->get_bot_info();
|
||||
td::string update_count;
|
||||
td::string request_count;
|
||||
auto replace_tabs = [](td::string &str) {
|
||||
for (auto &c : str) {
|
||||
if (c == '\t') {
|
||||
c = ' ';
|
||||
}
|
||||
}
|
||||
};
|
||||
auto stats = client_info->stat_.as_vector(now);
|
||||
for (auto &stat : stats) {
|
||||
if (stat.key_ == "update_count") {
|
||||
replace_tabs(stat.value_);
|
||||
update_count = std::move(stat.value_);
|
||||
}
|
||||
if (stat.key_ == "request_count") {
|
||||
replace_tabs(stat.value_);
|
||||
request_count = std::move(stat.value_);
|
||||
}
|
||||
}
|
||||
LOG(WARNING) << td::tag("id", bot_info.id_) << td::tag("update_count", update_count)
|
||||
<< td::tag("request_count", request_count);
|
||||
}
|
||||
}
|
||||
|
||||
void ClientManager::raw_event(const td::Event::Raw &event) {
|
||||
auto id = get_link_token();
|
||||
auto *info = clients_.get(id);
|
||||
CHECK(info != nullptr);
|
||||
CHECK(info->tqueue_id_ != 0);
|
||||
auto &value = active_client_count_[info->tqueue_id_];
|
||||
if (event.ptr != nullptr) {
|
||||
value++;
|
||||
} else {
|
||||
CHECK(value > 0);
|
||||
if (--value == 0) {
|
||||
active_client_count_.erase(info->tqueue_id_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ClientManager::timeout_expired() {
|
||||
send_closure(watchdog_id_, &Watchdog::kick);
|
||||
set_timeout_in(WATCHDOG_TIMEOUT / 10);
|
||||
|
||||
double now = td::Time::now();
|
||||
if (now > next_tqueue_gc_time_) {
|
||||
auto unix_time = parameters_->shared_data_->get_unix_time(now);
|
||||
LOG(INFO) << "Run TQueue GC at " << unix_time;
|
||||
td::int64 deleted_events;
|
||||
bool is_finished;
|
||||
std::tie(deleted_events, is_finished) = parameters_->shared_data_->tqueue_->run_gc(unix_time);
|
||||
LOG(INFO) << "TQueue GC deleted " << deleted_events << " events";
|
||||
next_tqueue_gc_time_ = td::Time::now() + (is_finished ? 60.0 : 1.0);
|
||||
|
||||
tqueue_deleted_events_ += deleted_events;
|
||||
if (tqueue_deleted_events_ > last_tqueue_deleted_events_ + 10000) {
|
||||
LOG(WARNING) << "TQueue GC already deleted " << tqueue_deleted_events_ << " events since the start";
|
||||
last_tqueue_deleted_events_ = tqueue_deleted_events_;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ClientManager::hangup_shared() {
|
||||
|
|
@ -380,18 +563,22 @@ void ClientManager::hangup_shared() {
|
|||
clients_.erase(id);
|
||||
|
||||
if (close_flag_ && clients_.empty()) {
|
||||
CHECK(active_client_count_.empty());
|
||||
close_db();
|
||||
}
|
||||
}
|
||||
|
||||
void ClientManager::close_db() {
|
||||
LOG(WARNING) << "Closing databases";
|
||||
td::MultiPromiseActorSafe mpromise("close binlogs");
|
||||
mpromise.add_promise(td::PromiseCreator::lambda(
|
||||
td::MultiPromiseActorSafe mpas("close binlogs");
|
||||
mpas.add_promise(td::PromiseCreator::lambda(
|
||||
[actor_id = actor_id(this)](td::Unit) { send_closure(actor_id, &ClientManager::finish_close); }));
|
||||
mpas.set_ignore_errors(true);
|
||||
|
||||
parameters_->shared_data_->tqueue_->close(mpromise.get_promise());
|
||||
parameters_->shared_data_->webhook_db_->close(mpromise.get_promise());
|
||||
auto lock = mpas.get_promise();
|
||||
parameters_->shared_data_->tqueue_->close(mpas.get_promise());
|
||||
parameters_->shared_data_->webhook_db_->close(mpas.get_promise());
|
||||
lock.set_value(td::Unit());
|
||||
}
|
||||
|
||||
void ClientManager::finish_close() {
|
||||
|
|
@ -403,4 +590,6 @@ void ClientManager::finish_close() {
|
|||
stop();
|
||||
}
|
||||
|
||||
constexpr double ClientManager::WATCHDOG_TIMEOUT;
|
||||
|
||||
} // namespace telegram_bot_api
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
|
@ -9,18 +9,19 @@
|
|||
#include "telegram-bot-api/Client.h"
|
||||
#include "telegram-bot-api/Query.h"
|
||||
#include "telegram-bot-api/Stats.h"
|
||||
#include "telegram-bot-api/Watchdog.h"
|
||||
|
||||
#include "td/actor/actor.h"
|
||||
#include "td/actor/PromiseFuture.h"
|
||||
|
||||
#include "td/utils/buffer.h"
|
||||
#include "td/utils/common.h"
|
||||
#include "td/utils/Container.h"
|
||||
#include "td/utils/FlatHashMap.h"
|
||||
#include "td/utils/FloodControlFast.h"
|
||||
#include "td/utils/Promise.h"
|
||||
#include "td/utils/Slice.h"
|
||||
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
namespace telegram_bot_api {
|
||||
|
|
@ -41,9 +42,11 @@ class ClientManager final : public td::Actor {
|
|||
: parameters_(std::move(parameters)), token_range_(token_range) {
|
||||
}
|
||||
|
||||
void dump_statistics();
|
||||
|
||||
void send(PromisedQueryPtr query);
|
||||
|
||||
void get_stats(td::PromiseActor<td::BufferSlice> promise, td::vector<std::pair<td::string, td::string>> args);
|
||||
void get_stats(td::Promise<td::BufferSlice> promise, td::vector<std::pair<td::string, td::string>> args);
|
||||
|
||||
void close(td::Promise<td::Unit> &&promise);
|
||||
|
||||
|
|
@ -52,6 +55,7 @@ class ClientManager final : public td::Actor {
|
|||
public:
|
||||
BotStatActor stat_;
|
||||
td::string token_;
|
||||
td::int64 tqueue_id_;
|
||||
td::ActorOwn<Client> client_;
|
||||
};
|
||||
td::Container<ClientInfo> clients_;
|
||||
|
|
@ -60,19 +64,35 @@ class ClientManager final : public td::Actor {
|
|||
std::shared_ptr<const ClientParameters> parameters_;
|
||||
TokenRange token_range_;
|
||||
|
||||
std::unordered_map<td::string, td::uint64> token_to_id_;
|
||||
std::unordered_map<td::string, td::FloodControlFast> flood_controls_;
|
||||
td::FlatHashMap<td::string, td::uint64> token_to_id_;
|
||||
td::FlatHashMap<td::string, td::FloodControlFast> flood_controls_;
|
||||
td::FlatHashMap<td::int64, td::uint64> active_client_count_;
|
||||
|
||||
bool close_flag_ = false;
|
||||
td::vector<td::Promise<td::Unit>> close_promises_;
|
||||
|
||||
td::ActorOwn<Watchdog> watchdog_id_;
|
||||
double next_tqueue_gc_time_ = 0.0;
|
||||
td::int64 tqueue_deleted_events_ = 0;
|
||||
td::int64 last_tqueue_deleted_events_ = 0;
|
||||
|
||||
static constexpr double WATCHDOG_TIMEOUT = 0.25;
|
||||
|
||||
static td::int64 get_tqueue_id(td::int64 user_id, bool is_test_dc);
|
||||
|
||||
static PromisedQueryPtr get_webhook_restore_query(td::Slice token, td::Slice webhook_info,
|
||||
std::shared_ptr<SharedData> shared_data);
|
||||
|
||||
void start_up() override;
|
||||
void hangup_shared() override;
|
||||
struct TopClients {
|
||||
td::int32 active_count = 0;
|
||||
td::vector<td::uint64> top_client_ids;
|
||||
};
|
||||
TopClients get_top_clients(std::size_t max_count, td::Slice token_filter);
|
||||
|
||||
void start_up() final;
|
||||
void raw_event(const td::Event::Raw &event) final;
|
||||
void timeout_expired() final;
|
||||
void hangup_shared() final;
|
||||
void close_db();
|
||||
void finish_close();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "td/actor/actor.h"
|
||||
|
||||
#include "td/db/KeyValueSyncInterface.h"
|
||||
#include "td/db/TQueue.h"
|
||||
|
||||
#include "td/net/GetHostByNameActor.h"
|
||||
|
||||
#include "td/actor/actor.h"
|
||||
|
||||
#include "td/utils/common.h"
|
||||
#include "td/utils/List.h"
|
||||
#include "td/utils/port/IPAddress.h"
|
||||
|
|
@ -23,15 +23,16 @@
|
|||
|
||||
namespace td {
|
||||
class NetQueryStats;
|
||||
}
|
||||
} // namespace td
|
||||
|
||||
namespace telegram_bot_api {
|
||||
|
||||
struct SharedData {
|
||||
std::atomic<td::uint64> query_count_{0};
|
||||
std::atomic<size_t> query_list_size_{0};
|
||||
std::atomic<int> next_verbosity_level_{-1};
|
||||
|
||||
// not thread-safe
|
||||
// not thread-safe, must be used from a single thread
|
||||
td::ListNode query_list_;
|
||||
td::unique_ptr<td::KeyValueSyncInterface> webhook_db_;
|
||||
td::unique_ptr<td::TQueue> tqueue_;
|
||||
|
|
@ -51,18 +52,72 @@ struct SharedData {
|
|||
}
|
||||
return static_cast<td::int32>(result);
|
||||
}
|
||||
|
||||
static td::int32 get_file_gc_scheduler_id() {
|
||||
// the same scheduler as for file GC in Td
|
||||
return 2;
|
||||
}
|
||||
|
||||
static td::int32 get_client_scheduler_id() {
|
||||
// the thread for ClientManager and all Clients
|
||||
return 4;
|
||||
}
|
||||
|
||||
static td::int32 get_watchdog_scheduler_id() {
|
||||
// the thread for watchdogs
|
||||
return 5;
|
||||
}
|
||||
|
||||
static td::int32 get_slow_incoming_http_scheduler_id() {
|
||||
// the thread for slow incoming HTTP connections
|
||||
return 6;
|
||||
}
|
||||
|
||||
static td::int32 get_slow_outgoing_http_scheduler_id() {
|
||||
// the thread for slow outgoing HTTP connections
|
||||
return 7;
|
||||
}
|
||||
|
||||
static td::int32 get_dns_resolver_scheduler_id() {
|
||||
// the thread for DNS resolving
|
||||
return 8;
|
||||
}
|
||||
|
||||
static td::int32 get_binlog_scheduler_id() {
|
||||
// the thread for TQueue and webhook binlogs
|
||||
return 9;
|
||||
}
|
||||
|
||||
static td::int32 get_webhook_certificate_scheduler_id() {
|
||||
// the thread for webhook certificate processing
|
||||
return 10;
|
||||
}
|
||||
|
||||
static td::int32 get_statistics_thread_id() {
|
||||
// the thread for CPU usage updating
|
||||
return 11;
|
||||
}
|
||||
|
||||
static td::int32 get_thread_count() {
|
||||
return 12;
|
||||
}
|
||||
};
|
||||
|
||||
struct ClientParameters {
|
||||
td::string working_directory_;
|
||||
bool allow_colon_in_filenames_ = true;
|
||||
|
||||
bool local_mode_ = false;
|
||||
|
||||
td::int32 api_id_ = 0;
|
||||
td::string api_hash_;
|
||||
|
||||
td::string version_;
|
||||
|
||||
td::int32 default_max_webhook_connections_ = 0;
|
||||
td::IPAddress webhook_proxy_ip_address_;
|
||||
|
||||
double start_timestamp_ = 0;
|
||||
double start_time_ = 0;
|
||||
|
||||
td::ActorId<td::GetHostByNameActor> get_host_by_name_actor_id_;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
|
@ -14,12 +14,14 @@
|
|||
#include "td/utils/JsonBuilder.h"
|
||||
#include "td/utils/logging.h"
|
||||
#include "td/utils/Parser.h"
|
||||
#include "td/utils/Promise.h"
|
||||
#include "td/utils/SliceBuilder.h"
|
||||
|
||||
namespace telegram_bot_api {
|
||||
|
||||
void HttpConnection::handle(td::unique_ptr<td::HttpQuery> http_query,
|
||||
td::ActorOwn<td::HttpInboundConnection> connection) {
|
||||
CHECK(connection_->empty());
|
||||
CHECK(connection_.empty());
|
||||
connection_ = std::move(connection);
|
||||
|
||||
LOG(DEBUG) << "Handle " << *http_query;
|
||||
|
|
@ -43,26 +45,21 @@ void HttpConnection::handle(td::unique_ptr<td::HttpQuery> http_query,
|
|||
}
|
||||
|
||||
auto method = url_path_parser.data();
|
||||
auto query = std::make_unique<Query>(std::move(http_query->container_), token, is_test_dc, method,
|
||||
std::move(http_query->args_), std::move(http_query->headers_),
|
||||
std::move(http_query->files_), shared_data_, http_query->peer_address_);
|
||||
auto query = td::make_unique<Query>(std::move(http_query->container_), token, is_test_dc, method,
|
||||
std::move(http_query->args_), std::move(http_query->headers_),
|
||||
std::move(http_query->files_), shared_data_, http_query->peer_address_, false);
|
||||
|
||||
td::PromiseActor<td::unique_ptr<Query>> promise;
|
||||
td::FutureActor<td::unique_ptr<Query>> future;
|
||||
td::init_promise_future(&promise, &future);
|
||||
future.set_event(td::EventCreator::yield(actor_id()));
|
||||
auto promise = td::PromiseCreator::lambda([actor_id = actor_id(this)](td::Result<td::unique_ptr<Query>> r_query) {
|
||||
send_closure(actor_id, &HttpConnection::on_query_finished, std::move(r_query));
|
||||
});
|
||||
auto promised_query = PromisedQueryPtr(query.release(), PromiseDeleter(std::move(promise)));
|
||||
send_closure(client_manager_, &ClientManager::send, std::move(promised_query));
|
||||
result_ = std::move(future);
|
||||
}
|
||||
|
||||
void HttpConnection::wakeup() {
|
||||
if (result_.empty()) {
|
||||
return;
|
||||
}
|
||||
LOG_CHECK(result_.is_ok()) << result_.move_as_error();
|
||||
void HttpConnection::on_query_finished(td::Result<td::unique_ptr<Query>> r_query) {
|
||||
LOG_CHECK(r_query.is_ok()) << r_query.error();
|
||||
|
||||
auto query = result_.move_as_ok();
|
||||
auto query = r_query.move_as_ok();
|
||||
send_response(query->http_status_code(), std::move(query->answer()), query->retry_after());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
|
@ -9,14 +9,14 @@
|
|||
#include "telegram-bot-api/ClientManager.h"
|
||||
#include "telegram-bot-api/Query.h"
|
||||
|
||||
#include "td/actor/actor.h"
|
||||
#include "td/actor/PromiseFuture.h"
|
||||
|
||||
#include "td/net/HttpInboundConnection.h"
|
||||
#include "td/net/HttpQuery.h"
|
||||
|
||||
#include "td/actor/actor.h"
|
||||
|
||||
#include "td/utils/buffer.h"
|
||||
#include "td/utils/Slice.h"
|
||||
#include "td/utils/Status.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
|
|
@ -24,27 +24,26 @@ namespace telegram_bot_api {
|
|||
|
||||
struct SharedData;
|
||||
|
||||
class HttpConnection : public td::HttpInboundConnection::Callback {
|
||||
class HttpConnection final : public td::HttpInboundConnection::Callback {
|
||||
public:
|
||||
explicit HttpConnection(td::ActorId<ClientManager> client_manager, std::shared_ptr<SharedData> shared_data)
|
||||
: client_manager_(client_manager), shared_data_(std::move(shared_data)) {
|
||||
}
|
||||
|
||||
void handle(td::unique_ptr<td::HttpQuery> http_query, td::ActorOwn<td::HttpInboundConnection> connection) override;
|
||||
|
||||
void wakeup() override;
|
||||
void handle(td::unique_ptr<td::HttpQuery> http_query, td::ActorOwn<td::HttpInboundConnection> connection) final;
|
||||
|
||||
private:
|
||||
td::FutureActor<td::unique_ptr<Query>> result_;
|
||||
td::ActorId<ClientManager> client_manager_;
|
||||
td::ActorOwn<td::HttpInboundConnection> connection_;
|
||||
std::shared_ptr<SharedData> shared_data_;
|
||||
|
||||
void hangup() override {
|
||||
void hangup() final {
|
||||
connection_.release();
|
||||
stop();
|
||||
}
|
||||
|
||||
void on_query_finished(td::Result<td::unique_ptr<Query>> r_query);
|
||||
|
||||
void send_response(int http_status_code, td::BufferSlice &&content, int retry_after);
|
||||
|
||||
void send_http_error(int http_status_code, td::Slice description);
|
||||
|
|
|
|||
|
|
@ -1,68 +1,74 @@
|
|||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "td/actor/actor.h"
|
||||
#include "telegram-bot-api/ClientParameters.h"
|
||||
|
||||
#include "td/net/HttpInboundConnection.h"
|
||||
#include "td/net/TcpListener.h"
|
||||
|
||||
#include "td/actor/actor.h"
|
||||
|
||||
#include "td/utils/BufferedFd.h"
|
||||
#include "td/utils/common.h"
|
||||
#include "td/utils/FloodControlFast.h"
|
||||
#include "td/utils/format.h"
|
||||
#include "td/utils/logging.h"
|
||||
#include "td/utils/port/SocketFd.h"
|
||||
#include "td/utils/SliceBuilder.h"
|
||||
#include "td/utils/Time.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace telegram_bot_api {
|
||||
|
||||
class HttpServer : public td::TcpListener::Callback {
|
||||
class HttpServer final : public td::TcpListener::Callback {
|
||||
public:
|
||||
HttpServer(int port, std::function<td::ActorOwn<td::HttpInboundConnection::Callback>()> creator)
|
||||
: port_(port), creator_(std::move(creator)) {
|
||||
HttpServer(td::string ip_address, int port,
|
||||
std::function<td::ActorOwn<td::HttpInboundConnection::Callback>()> creator)
|
||||
: ip_address_(std::move(ip_address)), port_(port), creator_(std::move(creator)) {
|
||||
flood_control_.add_limit(1, 1); // 1 in a second
|
||||
flood_control_.add_limit(60, 10); // 10 in a minute
|
||||
}
|
||||
|
||||
private:
|
||||
td::string ip_address_;
|
||||
td::int32 port_;
|
||||
std::function<td::ActorOwn<td::HttpInboundConnection::Callback>()> creator_;
|
||||
td::ActorOwn<td::TcpListener> listener_;
|
||||
td::FloodControlFast flood_control_;
|
||||
|
||||
void start_up() override {
|
||||
void start_up() final {
|
||||
auto now = td::Time::now();
|
||||
auto wakeup_at = flood_control_.get_wakeup_at();
|
||||
if (wakeup_at > now) {
|
||||
set_timeout_at(wakeup_at);
|
||||
return;
|
||||
}
|
||||
flood_control_.add_event(static_cast<td::int32>(now));
|
||||
LOG(INFO) << "Create tcp listener " << td::tag("port", port_);
|
||||
listener_ = td::create_actor<td::TcpListener>(PSLICE() << "TcpListener" << td::tag("port", port_), port_,
|
||||
actor_shared(this, 1));
|
||||
flood_control_.add_event(now);
|
||||
LOG(INFO) << "Create TCP listener " << td::tag("address", ip_address_) << td::tag("port", port_);
|
||||
listener_ = td::create_actor<td::TcpListener>(
|
||||
PSLICE() << "TcpListener" << td::tag("address", ip_address_) << td::tag("port", port_), port_,
|
||||
actor_shared(this, 1), ip_address_);
|
||||
}
|
||||
void hangup_shared() override {
|
||||
|
||||
void hangup_shared() final {
|
||||
LOG(ERROR) << "TCP listener was closed";
|
||||
listener_.release();
|
||||
yield();
|
||||
}
|
||||
void accept(td::SocketFd fd) override {
|
||||
auto scheduler_count = td::Scheduler::instance()->sched_count();
|
||||
auto scheduler_id = scheduler_count - 1;
|
||||
if (scheduler_id > 0) {
|
||||
scheduler_id--;
|
||||
}
|
||||
td::create_actor<td::HttpInboundConnection>("HttpInboundConnection", std::move(fd), 0, 20, 500, creator_(),
|
||||
scheduler_id)
|
||||
|
||||
void accept(td::SocketFd fd) final {
|
||||
td::create_actor<td::HttpInboundConnection>("HttpInboundConnection", td::BufferedFd<td::SocketFd>(std::move(fd)), 0,
|
||||
50, 500, creator_(), SharedData::get_slow_incoming_http_scheduler_id())
|
||||
.release();
|
||||
}
|
||||
void loop() override {
|
||||
|
||||
void loop() final {
|
||||
if (listener_.empty()) {
|
||||
start_up();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
|
@ -9,31 +9,29 @@
|
|||
#include "td/net/HttpHeaderCreator.h"
|
||||
|
||||
#include "td/utils/common.h"
|
||||
#include "td/utils/logging.h"
|
||||
#include "td/utils/Promise.h"
|
||||
|
||||
namespace telegram_bot_api {
|
||||
|
||||
void HttpStatConnection::handle(td::unique_ptr<td::HttpQuery> http_query,
|
||||
td::ActorOwn<td::HttpInboundConnection> connection) {
|
||||
CHECK(connection_->empty());
|
||||
CHECK(connection_.empty());
|
||||
connection_ = std::move(connection);
|
||||
|
||||
td::PromiseActor<td::BufferSlice> promise;
|
||||
td::FutureActor<td::BufferSlice> future;
|
||||
init_promise_future(&promise, &future);
|
||||
future.set_event(td::EventCreator::yield(actor_id()));
|
||||
LOG(DEBUG) << "SEND";
|
||||
auto promise = td::PromiseCreator::lambda([actor_id = actor_id(this)](td::Result<td::BufferSlice> result) {
|
||||
send_closure(actor_id, &HttpStatConnection::on_result, std::move(result));
|
||||
});
|
||||
send_closure(client_manager_, &ClientManager::get_stats, std::move(promise), http_query->get_args());
|
||||
result_ = std::move(future);
|
||||
}
|
||||
|
||||
void HttpStatConnection::wakeup() {
|
||||
if (result_.empty()) {
|
||||
void HttpStatConnection::on_result(td::Result<td::BufferSlice> result) {
|
||||
if (result.is_error()) {
|
||||
send_closure(connection_.release(), &td::HttpInboundConnection::write_error,
|
||||
td::Status::Error(500, "Internal Server Error: closing"));
|
||||
return;
|
||||
}
|
||||
LOG_CHECK(result_.is_ok()) << result_.move_as_error();
|
||||
|
||||
auto content = result_.move_as_ok();
|
||||
auto content = result.move_as_ok();
|
||||
td::HttpHeaderCreator hc;
|
||||
hc.init_status_line(200);
|
||||
hc.set_keep_alive();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
|
@ -8,30 +8,30 @@
|
|||
|
||||
#include "telegram-bot-api/ClientManager.h"
|
||||
|
||||
#include "td/actor/actor.h"
|
||||
#include "td/actor/PromiseFuture.h"
|
||||
|
||||
#include "td/net/HttpInboundConnection.h"
|
||||
#include "td/net/HttpQuery.h"
|
||||
|
||||
#include "td/actor/actor.h"
|
||||
|
||||
#include "td/utils/buffer.h"
|
||||
#include "td/utils/Status.h"
|
||||
|
||||
namespace telegram_bot_api {
|
||||
|
||||
class HttpStatConnection : public td::HttpInboundConnection::Callback {
|
||||
class HttpStatConnection final : public td::HttpInboundConnection::Callback {
|
||||
public:
|
||||
explicit HttpStatConnection(td::ActorId<ClientManager> client_manager) : client_manager_(client_manager) {
|
||||
}
|
||||
void handle(td::unique_ptr<td::HttpQuery> http_query, td::ActorOwn<td::HttpInboundConnection> connection) override;
|
||||
|
||||
void wakeup() override;
|
||||
void handle(td::unique_ptr<td::HttpQuery> http_query, td::ActorOwn<td::HttpInboundConnection> connection) final;
|
||||
|
||||
private:
|
||||
td::FutureActor<td::BufferSlice> result_;
|
||||
td::ActorId<ClientManager> client_manager_;
|
||||
td::ActorOwn<td::HttpInboundConnection> connection_;
|
||||
|
||||
void hangup() override {
|
||||
void on_result(td::Result<td::BufferSlice> result);
|
||||
|
||||
void hangup() final {
|
||||
connection_.release();
|
||||
stop();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
|
@ -14,42 +14,54 @@
|
|||
#include "td/utils/logging.h"
|
||||
#include "td/utils/misc.h"
|
||||
#include "td/utils/port/IPAddress.h"
|
||||
#include "td/utils/SliceBuilder.h"
|
||||
#include "td/utils/Time.h"
|
||||
|
||||
#include <numeric>
|
||||
|
||||
namespace telegram_bot_api {
|
||||
|
||||
std::unordered_map<td::string, std::unique_ptr<td::VirtuallyJsonable>> empty_parameters;
|
||||
td::FlatHashMap<td::string, td::unique_ptr<td::VirtuallyJsonable>> empty_parameters;
|
||||
|
||||
Query::Query(td::vector<td::BufferSlice> &&container, td::Slice token, bool is_test_dc, td::MutableSlice method,
|
||||
td::vector<std::pair<td::MutableSlice, td::MutableSlice>> &&args,
|
||||
td::vector<std::pair<td::MutableSlice, td::MutableSlice>> &&headers, td::vector<td::HttpFile> &&files,
|
||||
std::shared_ptr<SharedData> shared_data, const td::IPAddress &peer_address)
|
||||
std::shared_ptr<SharedData> shared_data, const td::IPAddress &peer_ip_address, bool is_internal)
|
||||
: state_(State::Query)
|
||||
, shared_data_(shared_data)
|
||||
, peer_address_(peer_address)
|
||||
, peer_ip_address_(peer_ip_address)
|
||||
, container_(std::move(container))
|
||||
, token_(token)
|
||||
, is_test_dc_(is_test_dc)
|
||||
, method_(method)
|
||||
, args_(std::move(args))
|
||||
, headers_(std::move(headers))
|
||||
, files_(std::move(files)) {
|
||||
, files_(std::move(files))
|
||||
, is_internal_(is_internal) {
|
||||
if (method_.empty()) {
|
||||
method_ = arg("method");
|
||||
}
|
||||
td::to_lower_inplace(method_);
|
||||
start_timestamp_ = td::Time::now();
|
||||
LOG(INFO) << "QUERY: create " << td::tag("ptr", this) << *this;
|
||||
LOG(INFO) << "Query " << this << ": " << *this;
|
||||
if (shared_data_) {
|
||||
shared_data_->query_count_++;
|
||||
shared_data_->query_count_.fetch_add(1, std::memory_order_relaxed);
|
||||
if (method_ != "getupdates") {
|
||||
shared_data_->query_list_size_.fetch_add(1, std::memory_order_relaxed);
|
||||
shared_data_->query_list_.put(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
td::string Query::get_peer_ip_address() const {
|
||||
if (peer_ip_address_.is_valid() && !peer_ip_address_.is_reserved()) { // external connection
|
||||
return peer_ip_address_.get_ip_str().str();
|
||||
} else {
|
||||
// invalid peer IP address or connection from the local network
|
||||
return get_header("x-real-ip").str();
|
||||
}
|
||||
}
|
||||
|
||||
td::int64 Query::query_size() const {
|
||||
return std::accumulate(
|
||||
container_.begin(), container_.end(), td::int64{0},
|
||||
|
|
@ -66,9 +78,14 @@ td::int64 Query::files_max_size() const {
|
|||
[](td::int64 acc, const td::HttpFile &file) { return td::max(acc, file.size); });
|
||||
}
|
||||
|
||||
void Query::set_stat_actor(td::ActorId<BotStatActor> stat_actor) {
|
||||
stat_actor_ = stat_actor;
|
||||
send_request_stat();
|
||||
}
|
||||
|
||||
void Query::set_ok(td::BufferSlice result) {
|
||||
CHECK(state_ == State::Query);
|
||||
LOG(INFO) << "QUERY: got ok " << td::tag("ptr", this) << td::tag("text", result.as_slice());
|
||||
LOG(INFO) << "Query " << this << ": " << td::tag("method", method_) << td::tag("text", result.as_slice());
|
||||
answer_ = std::move(result);
|
||||
state_ = State::OK;
|
||||
http_status_code_ = 200;
|
||||
|
|
@ -76,7 +93,7 @@ void Query::set_ok(td::BufferSlice result) {
|
|||
}
|
||||
|
||||
void Query::set_error(int http_status_code, td::BufferSlice result) {
|
||||
LOG(INFO) << "QUERY: got error " << td::tag("ptr", this) << td::tag("code", http_status_code)
|
||||
LOG(INFO) << "Query " << this << ": " << td::tag("method", method_) << td::tag("code", http_status_code)
|
||||
<< td::tag("text", result.as_slice());
|
||||
CHECK(state_ == State::Query);
|
||||
answer_ = std::move(result);
|
||||
|
|
@ -88,8 +105,8 @@ void Query::set_error(int http_status_code, td::BufferSlice result) {
|
|||
void Query::set_retry_after_error(int retry_after) {
|
||||
retry_after_ = retry_after;
|
||||
|
||||
std::unordered_map<td::string, std::unique_ptr<td::VirtuallyJsonable>> parameters;
|
||||
parameters.emplace("retry_after", std::make_unique<td::VirtuallyJsonableLong>(retry_after));
|
||||
td::FlatHashMap<td::string, td::unique_ptr<td::VirtuallyJsonable>> parameters;
|
||||
parameters.emplace("retry_after", td::make_unique<td::VirtuallyJsonableLong>(retry_after));
|
||||
set_error(429, td::json_encode<td::BufferSlice>(
|
||||
JsonQueryError(429, PSLICE() << "Too Many Requests: retry after " << retry_after, parameters)));
|
||||
}
|
||||
|
|
@ -98,9 +115,25 @@ td::StringBuilder &operator<<(td::StringBuilder &sb, const Query &query) {
|
|||
auto padded_time =
|
||||
td::lpad(PSTRING() << td::format::as_time(td::Time::now_cached() - query.start_timestamp()), 10, ' ');
|
||||
sb << "[bot" << td::rpad(query.token().str(), 46, ' ') << "][time:" << padded_time << ']'
|
||||
<< td::tag("method", td::lpad(query.method().str(), 20, ' '));
|
||||
<< td::tag("method", td::lpad(query.method().str(), 25, ' '));
|
||||
if (!query.args().empty()) {
|
||||
sb << td::oneline(PSLICE() << query.args());
|
||||
sb << '{';
|
||||
for (const auto &arg : query.args()) {
|
||||
sb << '[';
|
||||
if (arg.first.size() > 128) {
|
||||
sb << '<' << arg.first.size() << '>' << td::oneline(arg.first.substr(0, 128)) << "...";
|
||||
} else {
|
||||
sb << td::oneline(arg.first);
|
||||
}
|
||||
sb << ':';
|
||||
if (arg.second.size() > 4096) {
|
||||
sb << '<' << arg.second.size() << '>' << td::oneline(arg.second.substr(0, 4096)) << "...";
|
||||
} else {
|
||||
sb << td::oneline(arg.second);
|
||||
}
|
||||
sb << ']';
|
||||
}
|
||||
sb << '}';
|
||||
}
|
||||
if (!query.files().empty()) {
|
||||
sb << query.files();
|
||||
|
|
@ -108,12 +141,26 @@ td::StringBuilder &operator<<(td::StringBuilder &sb, const Query &query) {
|
|||
return sb;
|
||||
}
|
||||
|
||||
void Query::send_response_stat() {
|
||||
void Query::send_request_stat() const {
|
||||
if (stat_actor_.empty()) {
|
||||
return;
|
||||
}
|
||||
send_closure(stat_actor_, &BotStatActor::add_event<ServerBotStat::Request>,
|
||||
ServerBotStat::Request{query_size(), file_count(), files_size(), files_max_size()}, td::Time::now());
|
||||
}
|
||||
|
||||
void Query::send_response_stat() const {
|
||||
auto now = td::Time::now();
|
||||
if (now - start_timestamp_ >= 100.0 && !is_internal_) {
|
||||
LOG(WARNING) << "Answer too old query with code " << http_status_code_ << " and answer size " << answer_.size()
|
||||
<< ": " << *this;
|
||||
}
|
||||
|
||||
if (stat_actor_.empty()) {
|
||||
return;
|
||||
}
|
||||
send_closure(stat_actor_, &BotStatActor::add_event<ServerBotStat::Response>,
|
||||
ServerBotStat::Response{is_ok(), answer().size()}, td::Time::now());
|
||||
ServerBotStat::Response{state_ == State::OK, answer_.size(), file_count(), files_size()}, now);
|
||||
}
|
||||
|
||||
} // namespace telegram_bot_api
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
|
@ -8,81 +8,79 @@
|
|||
|
||||
#include "telegram-bot-api/ClientParameters.h"
|
||||
|
||||
#include "td/actor/actor.h"
|
||||
#include "td/actor/PromiseFuture.h"
|
||||
|
||||
#include "td/net/HttpFile.h"
|
||||
|
||||
#include "td/actor/actor.h"
|
||||
|
||||
#include "td/utils/buffer.h"
|
||||
#include "td/utils/common.h"
|
||||
#include "td/utils/FlatHashMap.h"
|
||||
#include "td/utils/JsonBuilder.h"
|
||||
#include "td/utils/List.h"
|
||||
#include "td/utils/port/IPAddress.h"
|
||||
#include "td/utils/Promise.h"
|
||||
#include "td/utils/Slice.h"
|
||||
#include "td/utils/StringBuilder.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
namespace telegram_bot_api {
|
||||
|
||||
class BotStatActor;
|
||||
|
||||
class Query : public td::ListNode {
|
||||
class Query final : public td::ListNode {
|
||||
public:
|
||||
enum class State : td::int8 { Query, OK, Error };
|
||||
|
||||
td::Slice token() const {
|
||||
return token_;
|
||||
}
|
||||
|
||||
bool is_test_dc() const {
|
||||
return is_test_dc_;
|
||||
}
|
||||
|
||||
td::Slice method() const {
|
||||
return method_;
|
||||
}
|
||||
|
||||
bool has_arg(td::Slice key) const {
|
||||
auto it = std::find_if(args_.begin(), args_.end(),
|
||||
[&key](const std::pair<td::MutableSlice, td::MutableSlice> &s) { return s.first == key; });
|
||||
return it != args_.end();
|
||||
}
|
||||
|
||||
td::MutableSlice arg(td::Slice key) const {
|
||||
auto it = std::find_if(args_.begin(), args_.end(),
|
||||
[&key](const std::pair<td::MutableSlice, td::MutableSlice> &s) { return s.first == key; });
|
||||
return it == args_.end() ? td::MutableSlice() : it->second;
|
||||
}
|
||||
|
||||
const td::vector<std::pair<td::MutableSlice, td::MutableSlice>> &args() const {
|
||||
return args_;
|
||||
}
|
||||
|
||||
td::Slice get_header(td::Slice key) const {
|
||||
auto it = std::find_if(headers_.begin(), headers_.end(),
|
||||
[&key](const std::pair<td::MutableSlice, td::MutableSlice> &s) { return s.first == key; });
|
||||
return it == headers_.end() ? td::Slice() : it->second;
|
||||
}
|
||||
|
||||
const td::HttpFile *file(td::Slice key) const {
|
||||
auto it = std::find_if(files_.begin(), files_.end(), [&key](const td::HttpFile &f) { return f.field_name == key; });
|
||||
return it == files_.end() ? nullptr : &*it;
|
||||
}
|
||||
|
||||
const td::vector<td::HttpFile> &files() const {
|
||||
return files_;
|
||||
}
|
||||
|
||||
const td::IPAddress &peer_address() const {
|
||||
return peer_address_;
|
||||
}
|
||||
|
||||
// for stats
|
||||
td::int32 file_count() const {
|
||||
return static_cast<td::int32>(files_.size());
|
||||
}
|
||||
|
||||
td::int64 query_size() const;
|
||||
|
||||
td::int64 files_size() const;
|
||||
|
||||
td::int64 files_max_size() const;
|
||||
td::string get_peer_ip_address() const;
|
||||
|
||||
td::BufferSlice &answer() {
|
||||
return answer_;
|
||||
|
|
@ -106,33 +104,26 @@ class Query : public td::ListNode {
|
|||
return state_ != State::Query;
|
||||
}
|
||||
|
||||
bool is_error() const {
|
||||
return state_ == State::Error;
|
||||
}
|
||||
|
||||
bool is_ok() const {
|
||||
return state_ == State::OK;
|
||||
}
|
||||
|
||||
bool is_internal() const {
|
||||
return is_internal_;
|
||||
}
|
||||
|
||||
void set_internal(bool is_internal) {
|
||||
is_internal_ = is_internal;
|
||||
}
|
||||
|
||||
Query(td::vector<td::BufferSlice> &&container, td::Slice token, bool is_test_dc, td::MutableSlice method,
|
||||
td::vector<std::pair<td::MutableSlice, td::MutableSlice>> &&args,
|
||||
td::vector<std::pair<td::MutableSlice, td::MutableSlice>> &&headers, td::vector<td::HttpFile> &&files,
|
||||
std::shared_ptr<SharedData> shared_data, const td::IPAddress &peer_address);
|
||||
std::shared_ptr<SharedData> shared_data, const td::IPAddress &peer_ip_address, bool is_internal);
|
||||
Query(const Query &) = delete;
|
||||
Query &operator=(const Query &) = delete;
|
||||
Query(Query &&) = delete;
|
||||
Query &operator=(Query &&) = delete;
|
||||
~Query() {
|
||||
if (shared_data_) {
|
||||
shared_data_->query_count_--;
|
||||
shared_data_->query_count_.fetch_sub(1, std::memory_order_relaxed);
|
||||
if (!empty()) {
|
||||
shared_data_->query_list_size_.fetch_sub(1, std::memory_order_relaxed);
|
||||
}
|
||||
td::Scheduler::instance()->destroy_on_scheduler(SharedData::get_file_gc_scheduler_id(), container_, args_,
|
||||
headers_, files_, answer_);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -140,16 +131,13 @@ class Query : public td::ListNode {
|
|||
return start_timestamp_;
|
||||
}
|
||||
|
||||
void set_stat_actor(td::ActorId<BotStatActor> stat_actor) {
|
||||
stat_actor_ = stat_actor;
|
||||
}
|
||||
void send_response_stat();
|
||||
void set_stat_actor(td::ActorId<BotStatActor> stat_actor);
|
||||
|
||||
private:
|
||||
State state_;
|
||||
std::shared_ptr<SharedData> shared_data_;
|
||||
double start_timestamp_;
|
||||
td::IPAddress peer_address_;
|
||||
td::IPAddress peer_ip_address_;
|
||||
td::ActorId<BotStatActor> stat_actor_;
|
||||
|
||||
// request
|
||||
|
|
@ -166,17 +154,30 @@ class Query : public td::ListNode {
|
|||
td::BufferSlice answer_;
|
||||
int http_status_code_ = 0;
|
||||
int retry_after_ = 0;
|
||||
|
||||
// for stats
|
||||
td::int32 file_count() const {
|
||||
return static_cast<td::int32>(files_.size());
|
||||
}
|
||||
|
||||
td::int64 query_size() const;
|
||||
|
||||
td::int64 files_max_size() const;
|
||||
|
||||
void send_request_stat() const;
|
||||
|
||||
void send_response_stat() const;
|
||||
};
|
||||
|
||||
td::StringBuilder &operator<<(td::StringBuilder &sb, const Query &query);
|
||||
|
||||
// fix for outdated C++14 libraries
|
||||
// https://stackoverflow.com/questions/26947704/implicit-conversion-failure-from-initializer-list
|
||||
extern std::unordered_map<td::string, std::unique_ptr<td::VirtuallyJsonable>> empty_parameters;
|
||||
extern td::FlatHashMap<td::string, td::unique_ptr<td::VirtuallyJsonable>> empty_parameters;
|
||||
|
||||
class JsonParameters : public td::Jsonable {
|
||||
class JsonParameters final : public td::Jsonable {
|
||||
public:
|
||||
explicit JsonParameters(const std::unordered_map<td::string, std::unique_ptr<td::VirtuallyJsonable>> ¶meters)
|
||||
explicit JsonParameters(const td::FlatHashMap<td::string, td::unique_ptr<td::VirtuallyJsonable>> ¶meters)
|
||||
: parameters_(parameters) {
|
||||
}
|
||||
void store(td::JsonValueScope *scope) const {
|
||||
|
|
@ -188,11 +189,11 @@ class JsonParameters : public td::Jsonable {
|
|||
}
|
||||
|
||||
private:
|
||||
const std::unordered_map<td::string, std::unique_ptr<td::VirtuallyJsonable>> ¶meters_;
|
||||
const td::FlatHashMap<td::string, td::unique_ptr<td::VirtuallyJsonable>> ¶meters_;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class JsonQueryOk : public td::Jsonable {
|
||||
class JsonQueryOk final : public td::Jsonable {
|
||||
public:
|
||||
JsonQueryOk(const T &result, td::Slice description) : result_(result), description_(description) {
|
||||
}
|
||||
|
|
@ -210,11 +211,11 @@ class JsonQueryOk : public td::Jsonable {
|
|||
td::Slice description_;
|
||||
};
|
||||
|
||||
class JsonQueryError : public td::Jsonable {
|
||||
class JsonQueryError final : public td::Jsonable {
|
||||
public:
|
||||
JsonQueryError(
|
||||
int error_code, td::Slice description,
|
||||
const std::unordered_map<td::string, std::unique_ptr<td::VirtuallyJsonable>> ¶meters = empty_parameters)
|
||||
const td::FlatHashMap<td::string, td::unique_ptr<td::VirtuallyJsonable>> ¶meters = empty_parameters)
|
||||
: error_code_(error_code), description_(description), parameters_(parameters) {
|
||||
}
|
||||
void store(td::JsonValueScope *scope) const {
|
||||
|
|
@ -230,12 +231,12 @@ class JsonQueryError : public td::Jsonable {
|
|||
private:
|
||||
int error_code_;
|
||||
td::Slice description_;
|
||||
const std::unordered_map<td::string, std::unique_ptr<td::VirtuallyJsonable>> ¶meters_;
|
||||
const td::FlatHashMap<td::string, td::unique_ptr<td::VirtuallyJsonable>> ¶meters_;
|
||||
};
|
||||
|
||||
class PromiseDeleter {
|
||||
public:
|
||||
explicit PromiseDeleter(td::PromiseActor<td::unique_ptr<Query>> &&promise) : promise_(std::move(promise)) {
|
||||
explicit PromiseDeleter(td::Promise<td::unique_ptr<Query>> &&promise) : promise_(std::move(promise)) {
|
||||
}
|
||||
PromiseDeleter() = default;
|
||||
PromiseDeleter(const PromiseDeleter &) = delete;
|
||||
|
|
@ -244,7 +245,7 @@ class PromiseDeleter {
|
|||
PromiseDeleter &operator=(PromiseDeleter &&) = default;
|
||||
void operator()(Query *raw_ptr) {
|
||||
td::unique_ptr<Query> query(raw_ptr); // now I cannot forget to delete this pointer
|
||||
if (!promise_.empty_promise()) {
|
||||
if (promise_) {
|
||||
if (!query->is_ready()) {
|
||||
query->set_retry_after_error(5);
|
||||
}
|
||||
|
|
@ -253,11 +254,11 @@ class PromiseDeleter {
|
|||
}
|
||||
}
|
||||
~PromiseDeleter() {
|
||||
CHECK(promise_.empty());
|
||||
CHECK(!promise_);
|
||||
}
|
||||
|
||||
private:
|
||||
td::PromiseActor<td::unique_ptr<Query>> promise_;
|
||||
td::Promise<td::unique_ptr<Query>> promise_;
|
||||
};
|
||||
using PromisedQueryPtr = std::unique_ptr<Query, PromiseDeleter>;
|
||||
|
||||
|
|
@ -269,7 +270,7 @@ void answer_query(const Jsonable &result, PromisedQueryPtr query, td::Slice desc
|
|||
|
||||
inline void fail_query(
|
||||
int http_status_code, td::Slice description, PromisedQueryPtr query,
|
||||
const std::unordered_map<td::string, std::unique_ptr<td::VirtuallyJsonable>> ¶meters = empty_parameters) {
|
||||
const td::FlatHashMap<td::string, td::unique_ptr<td::VirtuallyJsonable>> ¶meters = empty_parameters) {
|
||||
query->set_error(http_status_code,
|
||||
td::json_encode<td::BufferSlice>(JsonQueryError(http_status_code, description, parameters)));
|
||||
query.reset(); // send query into promise explicitly
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
|
@ -9,6 +9,7 @@
|
|||
#include "td/utils/common.h"
|
||||
#include "td/utils/logging.h"
|
||||
#include "td/utils/port/thread.h"
|
||||
#include "td/utils/SliceBuilder.h"
|
||||
#include "td/utils/StringBuilder.h"
|
||||
|
||||
namespace telegram_bot_api {
|
||||
|
|
@ -19,17 +20,24 @@ ServerCpuStat::ServerCpuStat() {
|
|||
}
|
||||
}
|
||||
|
||||
void ServerCpuStat::add_event(const td::CpuStat &cpu_stat, double now) {
|
||||
std::lock_guard<std::mutex> guard(mutex_);
|
||||
for (auto &stat : stat_) {
|
||||
stat.add_event(cpu_stat, now);
|
||||
void ServerCpuStat::update(double now) {
|
||||
auto r_cpu_stat = td::cpu_stat();
|
||||
if (r_cpu_stat.is_error()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto &cpu_stat = instance();
|
||||
std::lock_guard<std::mutex> guard(cpu_stat.mutex_);
|
||||
for (auto &stat : cpu_stat.stat_) {
|
||||
stat.add_event(r_cpu_stat.ok(), now);
|
||||
}
|
||||
LOG(WARNING) << "CPU usage: " << cpu_stat.stat_[1].get_stat(now).as_vector()[0].value_;
|
||||
}
|
||||
|
||||
td::string ServerCpuStat::get_description() const {
|
||||
td::string ServerCpuStat::get_description() {
|
||||
td::string res = "DURATION";
|
||||
for (auto &descr : DESCR) {
|
||||
res += "\t";
|
||||
res += '\t';
|
||||
res += descr;
|
||||
}
|
||||
return res;
|
||||
|
|
@ -37,7 +45,7 @@ td::string ServerCpuStat::get_description() const {
|
|||
|
||||
static td::string to_percentage(td::uint64 ticks, td::uint64 total_ticks) {
|
||||
static double multiplier = 100.0 * (td::thread::hardware_concurrency() ? td::thread::hardware_concurrency() : 1);
|
||||
return PSTRING() << (static_cast<double>(ticks) / static_cast<double>(total_ticks) * multiplier) << "%";
|
||||
return PSTRING() << (static_cast<double>(ticks) / static_cast<double>(total_ticks) * multiplier) << '%';
|
||||
}
|
||||
|
||||
td::vector<StatItem> CpuStat::as_vector() const {
|
||||
|
|
@ -140,7 +148,7 @@ td::vector<StatItem> BotStatActor::as_vector(double now) {
|
|||
return res;
|
||||
}
|
||||
|
||||
td::string BotStatActor::get_description() const {
|
||||
td::string BotStatActor::get_description() {
|
||||
td::string res = "DURATION";
|
||||
for (auto &descr : DESCR) {
|
||||
res += "\t";
|
||||
|
|
@ -149,6 +157,43 @@ td::string BotStatActor::get_description() const {
|
|||
return res;
|
||||
}
|
||||
|
||||
double BotStatActor::get_score(double now) {
|
||||
auto minute_stat = stat_[2].stat_duration(now);
|
||||
double minute_score = minute_stat.first.request_count_ + minute_stat.first.update_count_;
|
||||
if (minute_stat.second != 0) {
|
||||
minute_score /= minute_stat.second;
|
||||
}
|
||||
auto all_time_stat = stat_[0].stat_duration(now);
|
||||
double all_time_score = 0.01 * (all_time_stat.first.request_count_ + all_time_stat.first.update_count_);
|
||||
if (all_time_stat.second != 0) {
|
||||
all_time_score /= all_time_stat.second;
|
||||
}
|
||||
auto active_request_score = static_cast<double>(td::max(get_active_request_count() - 10, static_cast<td::int64>(0)));
|
||||
auto active_file_upload_score = static_cast<double>(get_active_file_upload_bytes()) * 1e-8;
|
||||
return minute_score + all_time_score + active_request_score + active_file_upload_score;
|
||||
}
|
||||
|
||||
double BotStatActor::get_minute_update_count(double now) {
|
||||
auto minute_stat = stat_[2].stat_duration(now);
|
||||
double result = minute_stat.first.update_count_;
|
||||
if (minute_stat.second != 0) {
|
||||
result /= minute_stat.second;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
td::int64 BotStatActor::get_active_request_count() const {
|
||||
return active_request_count_;
|
||||
}
|
||||
|
||||
td::int64 BotStatActor::get_active_file_upload_bytes() const {
|
||||
return active_file_upload_bytes_;
|
||||
}
|
||||
|
||||
td::int64 BotStatActor::get_active_file_upload_count() const {
|
||||
return active_file_upload_count_;
|
||||
}
|
||||
|
||||
bool BotStatActor::is_active(double now) const {
|
||||
return last_activity_timestamp_ > now - 86400;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
|
@ -9,7 +9,6 @@
|
|||
#include "td/actor/actor.h"
|
||||
|
||||
#include "td/utils/common.h"
|
||||
#include "td/utils/logging.h"
|
||||
#include "td/utils/port/Stat.h"
|
||||
#include "td/utils/Time.h"
|
||||
#include "td/utils/TimedStat.h"
|
||||
|
|
@ -49,16 +48,10 @@ class ServerCpuStat {
|
|||
static ServerCpuStat stat;
|
||||
return stat;
|
||||
}
|
||||
static void update(double now) {
|
||||
auto r_event = td::cpu_stat();
|
||||
if (r_event.is_error()) {
|
||||
return;
|
||||
}
|
||||
instance().add_event(r_event.ok(), now);
|
||||
LOG(WARNING) << "CPU usage: " << instance().stat_[1].get_stat(now).as_vector()[0].value_;
|
||||
}
|
||||
|
||||
td::string get_description() const;
|
||||
static void update(double now);
|
||||
|
||||
static td::string get_description();
|
||||
|
||||
td::vector<StatItem> as_vector(double now);
|
||||
|
||||
|
|
@ -71,8 +64,6 @@ class ServerCpuStat {
|
|||
td::TimedStat<CpuStat> stat_[SIZE];
|
||||
|
||||
ServerCpuStat();
|
||||
|
||||
void add_event(const td::CpuStat &stat, double now);
|
||||
};
|
||||
|
||||
class ServerBotInfo {
|
||||
|
|
@ -86,8 +77,7 @@ class ServerBotInfo {
|
|||
td::int32 tail_update_id_ = 0;
|
||||
td::int32 webhook_max_connections_ = 0;
|
||||
std::size_t pending_update_count_ = 0;
|
||||
double start_timestamp_ = 0;
|
||||
double last_query_timestamp_ = 0;
|
||||
double start_time_ = 0;
|
||||
};
|
||||
|
||||
struct ServerBotStat {
|
||||
|
|
@ -116,15 +106,17 @@ struct ServerBotStat {
|
|||
struct Response {
|
||||
bool ok_;
|
||||
size_t size_;
|
||||
td::int64 file_count_;
|
||||
td::int64 files_size_;
|
||||
};
|
||||
void on_event(const Response &answer) {
|
||||
void on_event(const Response &response) {
|
||||
response_count_++;
|
||||
if (answer.ok_) {
|
||||
if (response.ok_) {
|
||||
response_count_ok_++;
|
||||
} else {
|
||||
response_count_error_++;
|
||||
}
|
||||
response_bytes_ += static_cast<double>(answer.size_);
|
||||
response_bytes_ += static_cast<double>(response.size_);
|
||||
}
|
||||
|
||||
struct Request {
|
||||
|
|
@ -155,9 +147,9 @@ class BotStatActor final : public td::Actor {
|
|||
}
|
||||
|
||||
BotStatActor(const BotStatActor &) = delete;
|
||||
BotStatActor &operator=(const BotStatActor &other) = delete;
|
||||
BotStatActor &operator=(const BotStatActor &) = delete;
|
||||
BotStatActor(BotStatActor &&) = default;
|
||||
BotStatActor &operator=(BotStatActor &&other) {
|
||||
BotStatActor &operator=(BotStatActor &&other) noexcept {
|
||||
if (!empty()) {
|
||||
do_stop();
|
||||
}
|
||||
|
|
@ -166,7 +158,7 @@ class BotStatActor final : public td::Actor {
|
|||
parent_ = other.parent_;
|
||||
return *this;
|
||||
}
|
||||
~BotStatActor() override = default;
|
||||
~BotStatActor() final = default;
|
||||
|
||||
template <class EventT>
|
||||
void add_event(const EventT &event, double now) {
|
||||
|
|
@ -174,13 +166,25 @@ class BotStatActor final : public td::Actor {
|
|||
for (auto &stat : stat_) {
|
||||
stat.add_event(event, now);
|
||||
}
|
||||
on_event(event);
|
||||
if (!parent_.empty()) {
|
||||
send_closure(parent_, &BotStatActor::add_event<EventT>, event, now);
|
||||
}
|
||||
}
|
||||
|
||||
td::vector<StatItem> as_vector(double now);
|
||||
td::string get_description() const;
|
||||
|
||||
static td::string get_description();
|
||||
|
||||
double get_score(double now);
|
||||
|
||||
double get_minute_update_count(double now);
|
||||
|
||||
td::int64 get_active_request_count() const;
|
||||
|
||||
td::int64 get_active_file_upload_bytes() const;
|
||||
|
||||
td::int64 get_active_file_upload_count() const;
|
||||
|
||||
bool is_active(double now) const;
|
||||
|
||||
|
|
@ -192,6 +196,26 @@ class BotStatActor final : public td::Actor {
|
|||
td::TimedStat<ServerBotStat> stat_[SIZE];
|
||||
td::ActorId<BotStatActor> parent_;
|
||||
double last_activity_timestamp_ = -1e9;
|
||||
td::int64 active_request_count_ = 0;
|
||||
td::int64 active_file_upload_bytes_ = 0;
|
||||
td::int64 active_file_upload_count_ = 0;
|
||||
|
||||
void on_event(const ServerBotStat::Update &update) {
|
||||
}
|
||||
|
||||
void on_event(const ServerBotStat::Response &response) {
|
||||
active_request_count_--;
|
||||
active_file_upload_count_ -= response.file_count_;
|
||||
active_file_upload_bytes_ -= response.files_size_;
|
||||
CHECK(active_request_count_ >= 0);
|
||||
CHECK(active_file_upload_bytes_ >= 0);
|
||||
}
|
||||
|
||||
void on_event(const ServerBotStat::Request &request) {
|
||||
active_request_count_++;
|
||||
active_file_upload_count_ += request.file_count_;
|
||||
active_file_upload_bytes_ += request.files_size_;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace telegram_bot_api
|
||||
|
|
|
|||
28
telegram-bot-api/Watchdog.cpp
Normal file
28
telegram-bot-api/Watchdog.cpp
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#include "telegram-bot-api/Watchdog.h"
|
||||
|
||||
#include "td/utils/logging.h"
|
||||
#include "td/utils/Time.h"
|
||||
|
||||
namespace telegram_bot_api {
|
||||
|
||||
void Watchdog::kick() {
|
||||
auto now = td::Time::now();
|
||||
if (now >= last_kick_time_ + timeout_ && last_kick_time_ > 0 && GET_VERBOSITY_LEVEL() >= VERBOSITY_NAME(ERROR)) {
|
||||
LOG(ERROR) << get_name() << " timeout expired after " << now - last_kick_time_ << " seconds";
|
||||
td::thread::send_real_time_signal(main_thread_id_, 2);
|
||||
}
|
||||
last_kick_time_ = now;
|
||||
set_timeout_in(timeout_);
|
||||
}
|
||||
|
||||
void Watchdog::timeout_expired() {
|
||||
kick();
|
||||
}
|
||||
|
||||
} // namespace telegram_bot_api
|
||||
31
telegram-bot-api/Watchdog.h
Normal file
31
telegram-bot-api/Watchdog.h
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "td/actor/actor.h"
|
||||
|
||||
#include "td/utils/port/thread.h"
|
||||
|
||||
namespace telegram_bot_api {
|
||||
|
||||
class Watchdog final : public td::Actor {
|
||||
public:
|
||||
Watchdog(td::thread::id main_thread_id, double timeout) : main_thread_id_(main_thread_id), timeout_(timeout) {
|
||||
// watchdog is disabled until it is kicked for the first time
|
||||
}
|
||||
|
||||
void kick();
|
||||
|
||||
private:
|
||||
void timeout_expired() final;
|
||||
|
||||
td::thread::id main_thread_id_;
|
||||
double timeout_;
|
||||
double last_kick_time_ = 0.0;
|
||||
};
|
||||
|
||||
} // namespace telegram_bot_api
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
|
@ -8,17 +8,11 @@
|
|||
|
||||
#include "telegram-bot-api/ClientParameters.h"
|
||||
|
||||
#include "td/db/TQueue.h"
|
||||
|
||||
#include "td/net/GetHostByNameActor.h"
|
||||
#include "td/net/HttpHeaderCreator.h"
|
||||
#include "td/net/HttpProxy.h"
|
||||
#include "td/net/SslStream.h"
|
||||
#include "td/net/TransparentProxy.h"
|
||||
|
||||
#include "td/actor/actor.h"
|
||||
#include "td/actor/PromiseFuture.h"
|
||||
|
||||
#include "td/utils/base64.h"
|
||||
#include "td/utils/buffer.h"
|
||||
#include "td/utils/common.h"
|
||||
|
|
@ -26,25 +20,24 @@
|
|||
#include "td/utils/JsonBuilder.h"
|
||||
#include "td/utils/logging.h"
|
||||
#include "td/utils/misc.h"
|
||||
#include "td/utils/port/Clocks.h"
|
||||
#include "td/utils/port/IPAddress.h"
|
||||
#include "td/utils/port/SocketFd.h"
|
||||
#include "td/utils/Promise.h"
|
||||
#include "td/utils/Random.h"
|
||||
#include "td/utils/ScopeGuard.h"
|
||||
#include "td/utils/SliceBuilder.h"
|
||||
#include "td/utils/Span.h"
|
||||
#include "td/utils/Time.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
namespace telegram_bot_api {
|
||||
|
||||
static int VERBOSITY_NAME(webhook) = VERBOSITY_NAME(DEBUG);
|
||||
|
||||
std::atomic<td::uint64> WebhookActor::total_connections_count_{0};
|
||||
std::atomic<td::uint64> WebhookActor::total_connection_count_{0};
|
||||
|
||||
WebhookActor::WebhookActor(td::ActorShared<Callback> callback, td::int64 tqueue_id, td::HttpUrl url,
|
||||
td::string cert_path, td::int32 max_connections, bool from_db_flag,
|
||||
td::string cached_ip_address, bool fix_ip_address,
|
||||
td::string cached_ip_address, bool fix_ip_address, td::string secret_token,
|
||||
std::shared_ptr<const ClientParameters> parameters)
|
||||
: callback_(std::move(callback))
|
||||
, tqueue_id_(tqueue_id)
|
||||
|
|
@ -53,7 +46,8 @@ WebhookActor::WebhookActor(td::ActorShared<Callback> callback, td::int64 tqueue_
|
|||
, parameters_(std::move(parameters))
|
||||
, fix_ip_address_(fix_ip_address)
|
||||
, from_db_flag_(from_db_flag)
|
||||
, max_connections_(max_connections) {
|
||||
, max_connections_(max_connections)
|
||||
, secret_token_(std::move(secret_token)) {
|
||||
CHECK(max_connections_ > 0);
|
||||
|
||||
if (!cached_ip_address.empty()) {
|
||||
|
|
@ -75,6 +69,11 @@ WebhookActor::WebhookActor(td::ActorShared<Callback> callback, td::int64 tqueue_
|
|||
<< ", max_connections = " << max_connections_;
|
||||
}
|
||||
|
||||
WebhookActor::~WebhookActor() {
|
||||
td::Scheduler::instance()->destroy_on_scheduler(SharedData::get_file_gc_scheduler_id(), update_map_, queue_updates_,
|
||||
queues_, ssl_ctx_);
|
||||
}
|
||||
|
||||
void WebhookActor::relax_wakeup_at(double wakeup_at, const char *source) {
|
||||
if (wakeup_at_ == 0 || wakeup_at < wakeup_at_) {
|
||||
VLOG(webhook) << "Wake up in " << wakeup_at - td::Time::now() << " from " << source;
|
||||
|
|
@ -83,82 +82,83 @@ void WebhookActor::relax_wakeup_at(double wakeup_at, const char *source) {
|
|||
}
|
||||
|
||||
void WebhookActor::resolve_ip_address() {
|
||||
if (fix_ip_address_) {
|
||||
if (fix_ip_address_ || is_ip_address_being_resolved_) {
|
||||
return;
|
||||
}
|
||||
if (td::Time::now() < next_ip_address_resolve_timestamp_) {
|
||||
relax_wakeup_at(next_ip_address_resolve_timestamp_, "resolve_ip_address");
|
||||
if (td::Time::now() < next_ip_address_resolve_time_) {
|
||||
relax_wakeup_at(next_ip_address_resolve_time_, "resolve_ip_address");
|
||||
return;
|
||||
}
|
||||
|
||||
bool future_created = false;
|
||||
if (future_ip_address_.empty()) {
|
||||
td::PromiseActor<td::IPAddress> promise;
|
||||
init_promise_future(&promise, &future_ip_address_);
|
||||
future_created = true;
|
||||
send_closure(parameters_->get_host_by_name_actor_id_, &td::GetHostByNameActor::run, url_.host_, url_.port_, false,
|
||||
td::PromiseCreator::from_promise_actor(std::move(promise)));
|
||||
is_ip_address_being_resolved_ = true;
|
||||
auto promise = td::PromiseCreator::lambda([actor_id = actor_id(this)](td::Result<td::IPAddress> r_ip_address) {
|
||||
send_closure(actor_id, &WebhookActor::on_resolved_ip_address, std::move(r_ip_address));
|
||||
});
|
||||
send_closure(parameters_->get_host_by_name_actor_id_, &td::GetHostByNameActor::run, url_.host_, url_.port_, false,
|
||||
std::move(promise));
|
||||
}
|
||||
|
||||
void WebhookActor::on_resolved_ip_address(td::Result<td::IPAddress> r_ip_address) {
|
||||
CHECK(is_ip_address_being_resolved_);
|
||||
is_ip_address_being_resolved_ = false;
|
||||
|
||||
next_ip_address_resolve_time_ =
|
||||
td::Time::now() + IP_ADDRESS_CACHE_TIME + td::Random::fast(0, IP_ADDRESS_CACHE_TIME / 10);
|
||||
relax_wakeup_at(next_ip_address_resolve_time_, "on_resolved_ip_address");
|
||||
|
||||
SCOPE_EXIT {
|
||||
loop();
|
||||
};
|
||||
|
||||
if (r_ip_address.is_error()) {
|
||||
return on_error(r_ip_address.move_as_error());
|
||||
}
|
||||
|
||||
if (future_ip_address_.is_ready()) {
|
||||
next_ip_address_resolve_timestamp_ =
|
||||
td::Time::now() + IP_ADDRESS_CACHE_TIME + td::Random::fast(0, IP_ADDRESS_CACHE_TIME / 10);
|
||||
relax_wakeup_at(next_ip_address_resolve_timestamp_, "resolve_ip_address");
|
||||
|
||||
auto r_ip_address = future_ip_address_.move_as_result();
|
||||
if (r_ip_address.is_error()) {
|
||||
CHECK(!(r_ip_address.error() == td::Status::Error<td::FutureActor<td::IPAddress>::HANGUP_ERROR_CODE>()));
|
||||
return on_error(r_ip_address.move_as_error());
|
||||
}
|
||||
auto new_ip_address = r_ip_address.move_as_ok();
|
||||
if (!check_ip_address(new_ip_address)) {
|
||||
return on_error(td::Status::Error(PSLICE() << "IP address " << new_ip_address.get_ip_str() << " is reserved"));
|
||||
}
|
||||
if (!(ip_address_ == new_ip_address)) {
|
||||
VLOG(webhook) << "IP address has changed: " << ip_address_ << " --> " << new_ip_address;
|
||||
ip_address_ = new_ip_address;
|
||||
ip_generation_++;
|
||||
if (was_checked_) {
|
||||
on_webhook_verified();
|
||||
}
|
||||
}
|
||||
VLOG(webhook) << "IP address was verified";
|
||||
} else {
|
||||
if (future_created) {
|
||||
future_ip_address_.set_event(td::EventCreator::yield(actor_id()));
|
||||
auto new_ip_address = r_ip_address.move_as_ok();
|
||||
auto check_status = check_ip_address(new_ip_address);
|
||||
if (check_status.is_error()) {
|
||||
return on_error(std::move(check_status));
|
||||
}
|
||||
if (!(ip_address_ == new_ip_address)) {
|
||||
VLOG(webhook) << "IP address has changed: " << ip_address_ << " --> " << new_ip_address;
|
||||
ip_address_ = new_ip_address;
|
||||
ip_generation_++;
|
||||
if (was_checked_) {
|
||||
on_webhook_verified();
|
||||
}
|
||||
}
|
||||
VLOG(webhook) << "IP address was verified";
|
||||
}
|
||||
|
||||
void WebhookActor::on_ssl_context_created(td::Result<td::SslCtx> r_ssl_ctx) {
|
||||
if (r_ssl_ctx.is_error()) {
|
||||
create_webhook_error("Can't create an SSL context", r_ssl_ctx.move_as_error(), true);
|
||||
loop();
|
||||
return;
|
||||
}
|
||||
ssl_ctx_ = r_ssl_ctx.move_as_ok();
|
||||
VLOG(webhook) << "SSL context was created";
|
||||
loop();
|
||||
}
|
||||
|
||||
td::Status WebhookActor::create_connection() {
|
||||
if (!ip_address_.is_valid()) {
|
||||
VLOG(webhook) << "Can't create connection: IP address is not ready";
|
||||
return td::Status::Error("IP address is not ready");
|
||||
}
|
||||
CHECK(ip_address_.is_valid());
|
||||
if (parameters_->webhook_proxy_ip_address_.is_valid()) {
|
||||
auto r_proxy_socket_fd = td::SocketFd::open(parameters_->webhook_proxy_ip_address_);
|
||||
if (r_proxy_socket_fd.is_error()) {
|
||||
td::Slice error_message = "Can't connect to the webhook proxy";
|
||||
auto error = td::Status::Error(PSLICE() << error_message << ": " << r_proxy_socket_fd.error());
|
||||
VLOG(webhook) << error;
|
||||
on_webhook_error(error_message);
|
||||
on_error(td::Status::Error(error_message));
|
||||
return error;
|
||||
return create_webhook_error("Can't connect to the webhook proxy", r_proxy_socket_fd.move_as_error(), false);
|
||||
}
|
||||
if (!was_checked_) {
|
||||
TRY_STATUS(create_ssl_stream()); // check certificate
|
||||
|
||||
// verify webhook even we can't establish connection to the webhook
|
||||
was_checked_ = true;
|
||||
on_webhook_verified();
|
||||
}
|
||||
|
||||
VLOG(webhook) << "Create connection through proxy " << parameters_->webhook_proxy_ip_address_;
|
||||
class Callback : public td::TransparentProxy::Callback {
|
||||
class Callback final : public td::TransparentProxy::Callback {
|
||||
public:
|
||||
Callback(td::ActorId<WebhookActor> actor, td::int64 id) : actor_(actor), id_(id) {
|
||||
}
|
||||
void set_result(td::Result<td::SocketFd> result) override {
|
||||
void set_result(td::Result<td::BufferedFd<td::SocketFd>> result) final {
|
||||
send_closure(std::move(actor_), &WebhookActor::on_socket_ready_async, std::move(result), id_);
|
||||
CHECK(actor_.empty());
|
||||
}
|
||||
|
|
@ -168,10 +168,10 @@ td::Status WebhookActor::create_connection() {
|
|||
Callback &operator=(Callback &&) = delete;
|
||||
~Callback() {
|
||||
if (!actor_.empty()) {
|
||||
send_closure(std::move(actor_), &WebhookActor::on_socket_ready_async, td::Status::Error("Cancelled"), id_);
|
||||
send_closure(std::move(actor_), &WebhookActor::on_socket_ready_async, td::Status::Error("Canceled"), id_);
|
||||
}
|
||||
}
|
||||
void on_connected() override {
|
||||
void on_connected() final {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
|
|
@ -190,14 +190,22 @@ td::Status WebhookActor::create_connection() {
|
|||
|
||||
auto r_fd = td::SocketFd::open(ip_address_);
|
||||
if (r_fd.is_error()) {
|
||||
td::Slice error_message = "Can't connect to the webhook";
|
||||
auto error = td::Status::Error(PSLICE() << error_message << ": " << r_fd.error());
|
||||
VLOG(webhook) << error;
|
||||
on_webhook_error(error_message);
|
||||
on_error(r_fd.move_as_error());
|
||||
return error;
|
||||
return create_webhook_error("Can't connect to the webhook", r_fd.move_as_error(), false);
|
||||
}
|
||||
return create_connection(r_fd.move_as_ok());
|
||||
return create_connection(td::BufferedFd<td::SocketFd>(r_fd.move_as_ok()));
|
||||
}
|
||||
|
||||
td::Status WebhookActor::create_webhook_error(td::Slice error_message, td::Status &&result, bool is_public) {
|
||||
CHECK(result.is_error());
|
||||
auto error = td::Status::Error(PSLICE() << error_message << ": " << result);
|
||||
VLOG(webhook) << error;
|
||||
if (is_public) {
|
||||
on_webhook_error(PSLICE() << error_message << ": " << result.public_message());
|
||||
} else {
|
||||
on_webhook_error(error_message);
|
||||
}
|
||||
on_error(std::move(result));
|
||||
return error;
|
||||
}
|
||||
|
||||
td::Result<td::SslStream> WebhookActor::create_ssl_stream() {
|
||||
|
|
@ -205,31 +213,28 @@ td::Result<td::SslStream> WebhookActor::create_ssl_stream() {
|
|||
return td::SslStream();
|
||||
}
|
||||
|
||||
auto r_ssl_stream = td::SslStream::create(url_.host_, cert_path_, td::SslStream::VerifyPeer::On, !cert_path_.empty());
|
||||
CHECK(ssl_ctx_);
|
||||
auto r_ssl_stream = td::SslStream::create(url_.host_, ssl_ctx_, !cert_path_.empty());
|
||||
if (r_ssl_stream.is_error()) {
|
||||
td::Slice error_message = "Can't create an SSL connection";
|
||||
auto error = td::Status::Error(PSLICE() << error_message << ": " << r_ssl_stream.error());
|
||||
VLOG(webhook) << error;
|
||||
on_webhook_error(PSLICE() << error_message << ": " << r_ssl_stream.error().public_message());
|
||||
on_error(r_ssl_stream.move_as_error());
|
||||
return std::move(error);
|
||||
return create_webhook_error("Can't create an SSL connection", r_ssl_stream.move_as_error(), true);
|
||||
}
|
||||
return r_ssl_stream.move_as_ok();
|
||||
}
|
||||
|
||||
td::Status WebhookActor::create_connection(td::SocketFd fd) {
|
||||
td::Status WebhookActor::create_connection(td::BufferedFd<td::SocketFd> fd) {
|
||||
TRY_RESULT(ssl_stream, create_ssl_stream());
|
||||
|
||||
auto id = connections_.create(Connection());
|
||||
auto *conn = connections_.get(id);
|
||||
conn->actor_id_ = td::create_actor<td::HttpOutboundConnection>(
|
||||
PSLICE() << "Connect:" << id, std::move(fd), std::move(ssl_stream), std::numeric_limits<size_t>::max(), 20, 60,
|
||||
td::ActorShared<td::HttpOutboundConnection::Callback>(actor_id(this), id));
|
||||
PSLICE() << "Connect:" << id, std::move(fd), std::move(ssl_stream), 0, 50, 60,
|
||||
td::ActorShared<td::HttpOutboundConnection::Callback>(actor_id(this), id),
|
||||
SharedData::get_slow_outgoing_http_scheduler_id());
|
||||
conn->ip_generation_ = ip_generation_;
|
||||
conn->event_id_ = {};
|
||||
conn->id_ = id;
|
||||
ready_connections_.put(conn->to_list_node());
|
||||
total_connections_count_.fetch_add(1, std::memory_order_relaxed);
|
||||
total_connection_count_.fetch_add(1, std::memory_order_relaxed);
|
||||
|
||||
if (!was_checked_) {
|
||||
was_checked_ = true;
|
||||
|
|
@ -239,7 +244,7 @@ td::Status WebhookActor::create_connection(td::SocketFd fd) {
|
|||
return td::Status::OK();
|
||||
}
|
||||
|
||||
void WebhookActor::on_socket_ready_async(td::Result<td::SocketFd> r_fd, td::int64 id) {
|
||||
void WebhookActor::on_socket_ready_async(td::Result<td::BufferedFd<td::SocketFd>> r_fd, td::int64 id) {
|
||||
pending_sockets_.erase(id);
|
||||
if (r_fd.is_ok()) {
|
||||
VLOG(webhook) << "Socket " << id << " is ready";
|
||||
|
|
@ -253,7 +258,16 @@ void WebhookActor::on_socket_ready_async(td::Result<td::SocketFd> r_fd, td::int6
|
|||
}
|
||||
|
||||
void WebhookActor::create_new_connections() {
|
||||
size_t need_connections = queues_.size();
|
||||
if (!ip_address_.is_valid()) {
|
||||
VLOG(webhook) << "Can't create new connections: IP address is not ready";
|
||||
return;
|
||||
}
|
||||
if (url_.protocol_ != td::HttpUrl::Protocol::Http && !ssl_ctx_) {
|
||||
VLOG(webhook) << "Can't create new connections: SSL context is not ready";
|
||||
return;
|
||||
}
|
||||
|
||||
size_t need_connections = queue_updates_.size();
|
||||
if (need_connections > static_cast<size_t>(max_connections_)) {
|
||||
need_connections = max_connections_;
|
||||
}
|
||||
|
|
@ -264,7 +278,7 @@ void WebhookActor::create_new_connections() {
|
|||
auto now = td::Time::now();
|
||||
td::FloodControlFast *flood;
|
||||
bool active;
|
||||
if (last_success_timestamp_ + 10 < now) {
|
||||
if (last_success_time_ + 10 < now) {
|
||||
flood = &pending_new_connection_flood_;
|
||||
if (need_connections > 1) {
|
||||
need_connections = 1;
|
||||
|
|
@ -289,7 +303,7 @@ void WebhookActor::create_new_connections() {
|
|||
<< td::tag("after", td::format::as_time(wakeup_at - now));
|
||||
break;
|
||||
}
|
||||
flood->add_event(static_cast<td::int32>(now));
|
||||
flood->add_event(now);
|
||||
if (create_connection().is_error()) {
|
||||
relax_wakeup_at(now + 1.0, "create_new_connections error");
|
||||
return;
|
||||
|
|
@ -356,20 +370,21 @@ void WebhookActor::load_updates() {
|
|||
}
|
||||
VLOG(webhook) << "Trying to load new updates from offset " << tqueue_offset_;
|
||||
|
||||
auto offset = tqueue_offset_;
|
||||
auto limit = td::min(SharedData::TQUEUE_EVENT_BUFFER_SIZE, max_loaded_updates_ - queue_updates_.size());
|
||||
auto updates = mutable_span(parameters_->shared_data_->event_buffer_, limit);
|
||||
td::MutableSpan<td::TQueue::Event> updates(parameters_->shared_data_->event_buffer_, limit);
|
||||
|
||||
auto now = td::Time::now();
|
||||
auto unix_time_now = parameters_->shared_data_->get_unix_time(now);
|
||||
size_t total_size = 0;
|
||||
if (tqueue_offset_.empty()) {
|
||||
if (offset.empty()) {
|
||||
updates.truncate(0);
|
||||
} else {
|
||||
auto r_size = tqueue->get(tqueue_id_, tqueue_offset_, false, unix_time_now, updates);
|
||||
auto r_size = tqueue->get(tqueue_id_, offset, false, unix_time_now, updates);
|
||||
if (r_size.is_error()) {
|
||||
VLOG(webhook) << "Failed to get new updates: " << r_size.error();
|
||||
tqueue_offset_ = tqueue->get_head(tqueue_id_);
|
||||
r_size = tqueue->get(tqueue_id_, tqueue_offset_, false, unix_time_now, updates);
|
||||
offset = tqueue_offset_ = tqueue->get_head(tqueue_id_);
|
||||
r_size = tqueue->get(tqueue_id_, offset, false, unix_time_now, updates);
|
||||
r_size.ensure();
|
||||
}
|
||||
total_size = r_size.ok();
|
||||
|
|
@ -380,21 +395,22 @@ void WebhookActor::load_updates() {
|
|||
|
||||
for (auto &update : updates) {
|
||||
VLOG(webhook) << "Load update " << update.id;
|
||||
if (update_map_.find(update.id) != update_map_.end()) {
|
||||
LOG(ERROR) << "Receive duplicated event from tqueue " << update.id;
|
||||
CHECK(update.id.is_valid());
|
||||
auto &dest_ptr = update_map_[update.id];
|
||||
if (dest_ptr != nullptr) {
|
||||
LOG(ERROR) << "Receive duplicate event " << update.id << " from TQueue";
|
||||
continue;
|
||||
}
|
||||
auto &dest = update_map_[update.id];
|
||||
dest_ptr = td::make_unique<Update>();
|
||||
auto &dest = *dest_ptr;
|
||||
dest.id_ = update.id;
|
||||
dest.json_ = update.data.str();
|
||||
dest.state_ = Update::State::Begin;
|
||||
dest.delay_ = 1;
|
||||
dest.wakeup_at_ = now;
|
||||
CHECK(update.expires_at >= unix_time_now);
|
||||
dest.expires_at_ = update.expires_at;
|
||||
dest.queue_id_ = update.extra;
|
||||
tqueue_offset_ = update.id.next().move_as_ok();
|
||||
begin_updates_n_++;
|
||||
|
||||
if (dest.queue_id_ == 0) {
|
||||
dest.queue_id_ = unique_queue_id_++;
|
||||
|
|
@ -420,9 +436,10 @@ void WebhookActor::load_updates() {
|
|||
}
|
||||
}
|
||||
if (need_warning) {
|
||||
LOG(WARNING) << "Found " << updates.size() << " updates out of " << total_size - update_map_.size() << " + "
|
||||
<< update_map_.size() << " in " << queues_.size() << " queues after last error \""
|
||||
<< last_error_message_ << "\" at " << last_error_date_;
|
||||
LOG(WARNING) << "Loaded " << updates.size() << " updates out of " << total_size << ". Have " << update_map_.size()
|
||||
<< " updates loaded in " << queue_updates_.size() << " queues after last error \""
|
||||
<< last_error_message_ << "\" " << (last_error_time_ == 0 ? -1 : td::Time::now() - last_error_time_)
|
||||
<< " seconds ago";
|
||||
}
|
||||
|
||||
if (updates.size() == total_size && last_update_was_successful_) {
|
||||
|
|
@ -430,15 +447,16 @@ void WebhookActor::load_updates() {
|
|||
}
|
||||
|
||||
if (!updates.empty()) {
|
||||
VLOG(webhook) << "Loaded " << updates.size() << " new updates from offset " << tqueue_offset_
|
||||
<< " out of requested " << limit << ". Have total of " << update_map_.size() << " updates loaded in "
|
||||
<< queue_updates_.size() << " queues";
|
||||
VLOG(webhook) << "Loaded " << updates.size() << " new updates from offset " << offset << " out of requested "
|
||||
<< limit << ". Have total of " << update_map_.size() << " updates loaded in " << queue_updates_.size()
|
||||
<< " queues";
|
||||
}
|
||||
}
|
||||
|
||||
void WebhookActor::drop_event(td::TQueue::EventId event_id) {
|
||||
auto it = update_map_.find(event_id);
|
||||
auto queue_id = it->second.queue_id_;
|
||||
CHECK(it != update_map_.end());
|
||||
auto queue_id = it->second->queue_id_;
|
||||
update_map_.erase(it);
|
||||
|
||||
auto queue_updates_it = queue_updates_.find(queue_id);
|
||||
|
|
@ -449,8 +467,10 @@ void WebhookActor::drop_event(td::TQueue::EventId event_id) {
|
|||
if (queue_updates_it->second.event_ids.empty()) {
|
||||
queue_updates_.erase(queue_updates_it);
|
||||
} else {
|
||||
auto &update = update_map_[queue_updates_it->second.event_ids.front()];
|
||||
queues_.emplace(update.wakeup_at_, update.queue_id_);
|
||||
auto update_id = queue_updates_it->second.event_ids.front();
|
||||
CHECK(update_id.is_valid());
|
||||
auto &update = update_map_[update_id];
|
||||
queues_.emplace(update->wakeup_at_, update->queue_id_);
|
||||
}
|
||||
|
||||
parameters_->shared_data_->tqueue_->forget(tqueue_id_, event_id);
|
||||
|
|
@ -458,8 +478,13 @@ void WebhookActor::drop_event(td::TQueue::EventId event_id) {
|
|||
|
||||
void WebhookActor::on_update_ok(td::TQueue::EventId event_id) {
|
||||
last_update_was_successful_ = true;
|
||||
last_success_timestamp_ = td::Time::now();
|
||||
VLOG(webhook) << "Receive ok for update " << event_id;
|
||||
last_success_time_ = td::Time::now();
|
||||
|
||||
auto it = update_map_.find(event_id);
|
||||
CHECK(it != update_map_.end());
|
||||
|
||||
VLOG(webhook) << "Receive ok for update " << event_id << " in " << (last_success_time_ - it->second->last_send_time_)
|
||||
<< " seconds";
|
||||
|
||||
drop_event(event_id);
|
||||
}
|
||||
|
|
@ -469,24 +494,31 @@ void WebhookActor::on_update_error(td::TQueue::EventId event_id, td::Slice error
|
|||
double now = td::Time::now();
|
||||
|
||||
auto it = update_map_.find(event_id);
|
||||
if (parameters_->shared_data_->get_unix_time(now) > it->second.expires_at_) {
|
||||
CHECK(it != update_map_.end());
|
||||
CHECK(it->second != nullptr);
|
||||
auto &update = *it->second;
|
||||
|
||||
const int MAX_RETRY_AFTER = 3600;
|
||||
retry_after = td::clamp(retry_after, 0, MAX_RETRY_AFTER);
|
||||
int next_delay = update.delay_;
|
||||
int next_effective_delay = retry_after;
|
||||
if (retry_after == 0 && update.fail_count_ > 0) {
|
||||
auto max_timeout = td::Random::fast(WEBHOOK_MAX_RESEND_TIMEOUT, WEBHOOK_MAX_RESEND_TIMEOUT * 2);
|
||||
next_delay = td::min(max_timeout, next_delay * 2);
|
||||
next_effective_delay = next_delay;
|
||||
}
|
||||
if (parameters_->shared_data_->get_unix_time(now) + next_effective_delay > update.expires_at_) {
|
||||
LOG(WARNING) << "Drop update " << event_id << ": " << error;
|
||||
drop_event(event_id);
|
||||
return;
|
||||
}
|
||||
auto &update = it->second;
|
||||
update.state_ = Update::State::Begin;
|
||||
const int MAX_RETRY_AFTER = 3600;
|
||||
retry_after = td::clamp(retry_after, 0, MAX_RETRY_AFTER);
|
||||
update.delay_ = retry_after != 0
|
||||
? retry_after
|
||||
: td::min(WEBHOOK_MAX_RESEND_TIMEOUT, update.delay_ * (update.fail_count_ > 0 ? 2 : 1));
|
||||
update.wakeup_at_ = update.fail_count_ > 0 ? now + update.delay_ : now;
|
||||
update.delay_ = next_delay;
|
||||
update.wakeup_at_ = now + next_effective_delay;
|
||||
update.fail_count_++;
|
||||
queues_.emplace(update.wakeup_at_, update.queue_id_);
|
||||
VLOG(webhook) << "Delay update " << event_id << " for " << (update.wakeup_at_ - now) << " seconds because of "
|
||||
<< error << " after " << update.fail_count_ << " fails";
|
||||
begin_updates_n_++;
|
||||
<< error << " after " << update.fail_count_ << " fails received in " << (now - update.last_send_time_)
|
||||
<< " seconds";
|
||||
}
|
||||
|
||||
td::Status WebhookActor::send_update() {
|
||||
|
|
@ -495,19 +527,26 @@ td::Status WebhookActor::send_update() {
|
|||
}
|
||||
|
||||
if (queues_.empty()) {
|
||||
return td::Status::Error("No updates");
|
||||
return td::Status::Error("No pending updates");
|
||||
}
|
||||
auto it = queues_.begin();
|
||||
if (it->wakeup_at > td::Time::now()) {
|
||||
auto now = td::Time::now();
|
||||
if (it->wakeup_at > now) {
|
||||
relax_wakeup_at(it->wakeup_at, "send_update");
|
||||
return td::Status::Error("No ready updates");
|
||||
}
|
||||
|
||||
auto queue_id = it->id;
|
||||
CHECK(queue_id != 0);
|
||||
queues_.erase(it);
|
||||
auto event_id = queue_updates_[queue_id].event_ids.front();
|
||||
CHECK(event_id.is_valid());
|
||||
|
||||
auto &update = update_map_[event_id];
|
||||
auto update_map_it = update_map_.find(event_id);
|
||||
CHECK(update_map_it != update_map_.end());
|
||||
CHECK(update_map_it->second != nullptr);
|
||||
auto &update = *update_map_it->second;
|
||||
update.last_send_time_ = now;
|
||||
|
||||
auto body = td::json_encode<td::BufferSlice>(JsonUpdate(update.id_.value(), update.json_));
|
||||
|
||||
|
|
@ -517,6 +556,9 @@ td::Status WebhookActor::send_update() {
|
|||
if (!url_.userinfo_.empty()) {
|
||||
hc.add_header("Authorization", PSLICE() << "Basic " << td::base64_encode(url_.userinfo_));
|
||||
}
|
||||
if (!secret_token_.empty()) {
|
||||
hc.add_header("X-Telegram-Bot-Api-Secret-Token", secret_token_);
|
||||
}
|
||||
hc.set_content_type("application/json");
|
||||
hc.set_content_size(body.size());
|
||||
hc.set_keep_alive();
|
||||
|
|
@ -527,8 +569,6 @@ td::Status WebhookActor::send_update() {
|
|||
}
|
||||
|
||||
auto &connection = *Connection::from_list_node(ready_connections_.get());
|
||||
begin_updates_n_--;
|
||||
update.state_ = Update::State::Send;
|
||||
connection.event_id_ = update.id_;
|
||||
|
||||
VLOG(webhook) << "Send update " << update.id_ << " from queue " << queue_id << " into connection " << connection.id_
|
||||
|
|
@ -542,15 +582,18 @@ td::Status WebhookActor::send_update() {
|
|||
}
|
||||
|
||||
void WebhookActor::send_updates() {
|
||||
VLOG(webhook) << "Have " << begin_updates_n_ << " pending updates to send";
|
||||
while (begin_updates_n_ > 0) {
|
||||
if (send_update().is_error()) {
|
||||
return;
|
||||
}
|
||||
VLOG(webhook) << "Have " << (queues_.size() + update_map_.size() - queue_updates_.size()) << " pending updates in "
|
||||
<< queues_.size() << " queues to send";
|
||||
while (send_update().is_ok()) {
|
||||
}
|
||||
}
|
||||
|
||||
void WebhookActor::handle(td::unique_ptr<td::HttpQuery> response) {
|
||||
SCOPE_EXIT {
|
||||
bool dummy = false;
|
||||
td::Scheduler::instance()->destroy_on_scheduler(SharedData::get_file_gc_scheduler_id(), response, dummy);
|
||||
};
|
||||
|
||||
auto connection_id = get_link_token();
|
||||
if (response) {
|
||||
VLOG(webhook) << "Got response from connection " << connection_id;
|
||||
|
|
@ -579,12 +622,13 @@ void WebhookActor::handle(td::unique_ptr<td::HttpQuery> response) {
|
|||
if (!method.empty() && method != "deletewebhook" && method != "setwebhook" && method != "close" &&
|
||||
method != "logout" && !td::begins_with(method, "get")) {
|
||||
VLOG(webhook) << "Receive request " << method << " in response to webhook";
|
||||
auto query =
|
||||
std::make_unique<Query>(std::move(response->container_), td::MutableSlice(), false, td::MutableSlice(),
|
||||
std::move(response->args_), std::move(response->headers_),
|
||||
std::move(response->files_), parameters_->shared_data_, response->peer_address_);
|
||||
auto promised_query =
|
||||
PromisedQueryPtr(query.release(), PromiseDeleter(td::PromiseActor<td::unique_ptr<Query>>()));
|
||||
response->container_.emplace_back(PSLICE() << (tqueue_id_ & ((static_cast<td::int64>(1) << 54) - 1)));
|
||||
auto token = response->container_.back().as_slice();
|
||||
auto query = td::make_unique<Query>(
|
||||
std::move(response->container_), token, tqueue_id_ >= (static_cast<td::int64>(1) << 54),
|
||||
td::MutableSlice(), std::move(response->args_), std::move(response->headers_),
|
||||
std::move(response->files_), parameters_->shared_data_, response->peer_address_, false);
|
||||
auto promised_query = PromisedQueryPtr(query.release(), PromiseDeleter(td::Promise<td::unique_ptr<Query>>()));
|
||||
send_closure(callback_, &Callback::send, std::move(promised_query));
|
||||
}
|
||||
first_error_410_time_ = 0;
|
||||
|
|
@ -632,7 +676,7 @@ void WebhookActor::handle(td::unique_ptr<td::HttpQuery> response) {
|
|||
if (need_close || close_connection) {
|
||||
VLOG(webhook) << "Close connection " << connection_id;
|
||||
connections_.erase(connection_ptr->id_);
|
||||
total_connections_count_.fetch_sub(1, std::memory_order_relaxed);
|
||||
total_connection_count_.fetch_sub(1, std::memory_order_relaxed);
|
||||
} else {
|
||||
ready_connections_.put(connection_ptr->to_list_node());
|
||||
}
|
||||
|
|
@ -647,11 +691,16 @@ void WebhookActor::handle(td::unique_ptr<td::HttpQuery> response) {
|
|||
void WebhookActor::start_up() {
|
||||
max_loaded_updates_ = max_connections_ * 2;
|
||||
|
||||
last_success_timestamp_ = td::Time::now();
|
||||
active_new_connection_flood_.add_limit(1, 10 * max_connections_);
|
||||
active_new_connection_flood_.add_limit(5, 20 * max_connections_);
|
||||
last_success_time_ = td::Time::now() - 2 * IP_ADDRESS_CACHE_TIME;
|
||||
if (from_db_flag_) {
|
||||
next_ip_address_resolve_time_ = td::Time::now() + td::Random::fast(0, IP_ADDRESS_CACHE_TIME);
|
||||
} else {
|
||||
next_ip_address_resolve_time_ = last_success_time_;
|
||||
}
|
||||
|
||||
pending_new_connection_flood_.add_limit(1, 1);
|
||||
active_new_connection_flood_.add_limit(0.5, 10);
|
||||
|
||||
pending_new_connection_flood_.add_limit(2, 1);
|
||||
|
||||
if (!parameters_->local_mode_) {
|
||||
if (url_.protocol_ == td::HttpUrl::Protocol::Https) {
|
||||
|
|
@ -662,15 +711,14 @@ void WebhookActor::start_up() {
|
|||
} else {
|
||||
CHECK(url_.protocol_ == td::HttpUrl::Protocol::Http);
|
||||
VLOG(webhook) << "Can't create connection: HTTP is forbidden";
|
||||
on_error(td::Status::Error("HTTPS url must be provided for webhook"));
|
||||
on_error(td::Status::Error("An HTTPS URL must be provided for webhook"));
|
||||
}
|
||||
}
|
||||
|
||||
if (fix_ip_address_ && !stop_flag_) {
|
||||
if (!ip_address_.is_valid()) {
|
||||
on_error(td::Status::Error("Invalid IP address specified"));
|
||||
} else if (!check_ip_address(ip_address_)) {
|
||||
on_error(td::Status::Error(PSLICE() << "IP address " << ip_address_.get_ip_str() << " is reserved"));
|
||||
auto check_status = check_ip_address(ip_address_);
|
||||
if (check_status.is_error()) {
|
||||
return on_error(std::move(check_status));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -679,6 +727,16 @@ void WebhookActor::start_up() {
|
|||
on_webhook_verified();
|
||||
}
|
||||
|
||||
if (url_.protocol_ != td::HttpUrl::Protocol::Http && !stop_flag_) {
|
||||
// asynchronously create SSL context
|
||||
td::Scheduler::instance()->run_on_scheduler(SharedData::get_webhook_certificate_scheduler_id(),
|
||||
[actor_id = actor_id(this), cert_path = cert_path_](td::Unit) mutable {
|
||||
send_closure(
|
||||
actor_id, &WebhookActor::on_ssl_context_created,
|
||||
td::SslCtx::create(cert_path, td::SslCtx::VerifyPeer::On));
|
||||
});
|
||||
}
|
||||
|
||||
yield();
|
||||
}
|
||||
|
||||
|
|
@ -700,7 +758,7 @@ void WebhookActor::close() {
|
|||
}
|
||||
|
||||
void WebhookActor::tear_down() {
|
||||
total_connections_count_.fetch_sub(connections_.size(), std::memory_order_relaxed);
|
||||
total_connection_count_.fetch_sub(connections_.size(), std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void WebhookActor::on_webhook_verified() {
|
||||
|
|
@ -711,24 +769,26 @@ void WebhookActor::on_webhook_verified() {
|
|||
send_closure(callback_, &Callback::webhook_verified, std::move(ip_address_str));
|
||||
}
|
||||
|
||||
bool WebhookActor::check_ip_address(const td::IPAddress &addr) const {
|
||||
td::Status WebhookActor::check_ip_address(const td::IPAddress &addr) const {
|
||||
if (!addr.is_valid()) {
|
||||
return false;
|
||||
return td::Status::Error("Invalid IP address specified");
|
||||
}
|
||||
if (parameters_->local_mode_) {
|
||||
// allow any valid IP address
|
||||
return true;
|
||||
return td::Status::OK();
|
||||
}
|
||||
if (!addr.is_ipv4()) {
|
||||
VLOG(webhook) << "Bad IP address (not IPv4): " << addr;
|
||||
return false;
|
||||
return td::Status::Error("IPv6-only addresses are not allowed");
|
||||
}
|
||||
return !addr.is_reserved();
|
||||
if (addr.is_reserved()) {
|
||||
return td::Status::Error(PSLICE() << "IP address " << addr.get_ip_str() << " is reserved");
|
||||
}
|
||||
return td::Status::OK();
|
||||
}
|
||||
|
||||
void WebhookActor::on_error(td::Status status) {
|
||||
VLOG(webhook) << "Receive webhook error " << status;
|
||||
if (!was_checked_) {
|
||||
if (!was_checked_ && !stop_flag_) {
|
||||
CHECK(!callback_.empty());
|
||||
send_closure(std::move(callback_), &Callback::webhook_closed, std::move(status));
|
||||
stop_flag_ = true;
|
||||
|
|
@ -743,7 +803,7 @@ void WebhookActor::on_connection_error(td::Status error) {
|
|||
void WebhookActor::on_webhook_error(td::Slice error) {
|
||||
if (was_checked_) {
|
||||
send_closure(callback_, &Callback::webhook_error, td::Status::Error(error));
|
||||
last_error_date_ = td::Clocks::system(); // local time to output it to the log
|
||||
last_error_time_ = td::Time::now();
|
||||
last_error_message_ = error.str();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
|
@ -12,14 +12,17 @@
|
|||
|
||||
#include "td/net/HttpOutboundConnection.h"
|
||||
#include "td/net/HttpQuery.h"
|
||||
#include "td/net/SslCtx.h"
|
||||
#include "td/net/SslStream.h"
|
||||
|
||||
#include "td/actor/actor.h"
|
||||
#include "td/actor/PromiseFuture.h"
|
||||
|
||||
#include "td/utils/BufferedFd.h"
|
||||
#include "td/utils/common.h"
|
||||
#include "td/utils/Container.h"
|
||||
#include "td/utils/FlatHashMap.h"
|
||||
#include "td/utils/FloodControlFast.h"
|
||||
#include "td/utils/HashTableUtils.h"
|
||||
#include "td/utils/HttpUrl.h"
|
||||
#include "td/utils/JsonBuilder.h"
|
||||
#include "td/utils/List.h"
|
||||
|
|
@ -30,17 +33,15 @@
|
|||
#include "td/utils/VectorQueue.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace telegram_bot_api {
|
||||
|
||||
struct ClientParameters;
|
||||
|
||||
class WebhookActor : public td::HttpOutboundConnection::Callback {
|
||||
class WebhookActor final : public td::HttpOutboundConnection::Callback {
|
||||
public:
|
||||
class Callback : public td::Actor {
|
||||
public:
|
||||
|
|
@ -53,14 +54,19 @@ class WebhookActor : public td::HttpOutboundConnection::Callback {
|
|||
|
||||
WebhookActor(td::ActorShared<Callback> callback, td::int64 tqueue_id, td::HttpUrl url, td::string cert_path,
|
||||
td::int32 max_connections, bool from_db_flag, td::string cached_ip_address, bool fix_ip_address,
|
||||
std::shared_ptr<const ClientParameters> parameters);
|
||||
td::string secret_token, std::shared_ptr<const ClientParameters> parameters);
|
||||
WebhookActor(const WebhookActor &) = delete;
|
||||
WebhookActor &operator=(const WebhookActor &) = delete;
|
||||
WebhookActor(WebhookActor &&) = delete;
|
||||
WebhookActor &operator=(WebhookActor &&) = delete;
|
||||
~WebhookActor();
|
||||
|
||||
void update();
|
||||
|
||||
void close();
|
||||
|
||||
static td::int64 get_total_connections_count() {
|
||||
return total_connections_count_;
|
||||
static td::int64 get_total_connection_count() {
|
||||
return total_connection_count_;
|
||||
}
|
||||
|
||||
private:
|
||||
|
|
@ -69,17 +75,17 @@ class WebhookActor : public td::HttpOutboundConnection::Callback {
|
|||
static constexpr int WEBHOOK_MAX_RESEND_TIMEOUT = 60;
|
||||
static constexpr int WEBHOOK_DROP_TIMEOUT = 60 * 60 * 23;
|
||||
|
||||
static std::atomic<td::uint64> total_connections_count_;
|
||||
static std::atomic<td::uint64> total_connection_count_;
|
||||
|
||||
td::ActorShared<Callback> callback_;
|
||||
td::int64 tqueue_id_;
|
||||
bool tqueue_empty_ = false;
|
||||
std::size_t last_pending_update_count_ = MIN_PENDING_UPDATES_WARNING;
|
||||
td::HttpUrl url_;
|
||||
td::string cert_path_;
|
||||
const td::string cert_path_;
|
||||
std::shared_ptr<const ClientParameters> parameters_;
|
||||
|
||||
double last_error_date_ = 0;
|
||||
double last_error_time_ = 0;
|
||||
td::string last_error_message_ = "<none>";
|
||||
|
||||
bool fix_ip_address_ = false;
|
||||
|
|
@ -94,11 +100,11 @@ class WebhookActor : public td::HttpOutboundConnection::Callback {
|
|||
td::TQueue::EventId id_;
|
||||
td::string json_;
|
||||
td::int32 expires_at_ = 0;
|
||||
double last_send_time_ = 0;
|
||||
double wakeup_at_ = 0;
|
||||
int delay_ = 0;
|
||||
int fail_count_ = 0;
|
||||
enum State { Begin, Send } state_ = State::Begin;
|
||||
td::int64 queue_id_{0};
|
||||
td::int64 queue_id_ = 0;
|
||||
};
|
||||
|
||||
struct QueueUpdates {
|
||||
|
|
@ -119,28 +125,27 @@ class WebhookActor : public td::HttpOutboundConnection::Callback {
|
|||
}
|
||||
};
|
||||
|
||||
td::int32 begin_updates_n_ = 0;
|
||||
|
||||
td::TQueue::EventId tqueue_offset_;
|
||||
std::size_t max_loaded_updates_ = 0;
|
||||
struct EventIdHash {
|
||||
std::size_t operator()(td::TQueue::EventId event_id) const {
|
||||
return std::hash<td::int32>()(event_id.value());
|
||||
td::uint32 operator()(td::TQueue::EventId event_id) const {
|
||||
return td::Hash<td::int32>()(event_id.value());
|
||||
}
|
||||
};
|
||||
std::unordered_map<td::TQueue::EventId, Update, EventIdHash> update_map_;
|
||||
std::unordered_map<td::int64, QueueUpdates> queue_updates_;
|
||||
td::FlatHashMap<td::TQueue::EventId, td::unique_ptr<Update>, EventIdHash> update_map_;
|
||||
td::FlatHashMap<td::int64, QueueUpdates> queue_updates_;
|
||||
std::set<Queue> queues_;
|
||||
td::int64 unique_queue_id_ = static_cast<td::int64>(1) << 60;
|
||||
|
||||
double first_error_410_time_ = 0;
|
||||
|
||||
td::SslCtx ssl_ctx_;
|
||||
td::IPAddress ip_address_;
|
||||
td::int32 ip_generation_ = 0;
|
||||
double next_ip_address_resolve_timestamp_ = 0;
|
||||
td::FutureActor<td::IPAddress> future_ip_address_;
|
||||
double next_ip_address_resolve_time_ = 0;
|
||||
bool is_ip_address_being_resolved_ = false;
|
||||
|
||||
class Connection : public td::ListNode {
|
||||
class Connection final : public td::ListNode {
|
||||
public:
|
||||
Connection() = default;
|
||||
Connection(const Connection &) = delete;
|
||||
|
|
@ -161,25 +166,31 @@ class WebhookActor : public td::HttpOutboundConnection::Callback {
|
|||
}
|
||||
};
|
||||
td::Container<td::ActorOwn<>> pending_sockets_;
|
||||
td::vector<td::SocketFd> ready_sockets_;
|
||||
td::vector<td::BufferedFd<td::SocketFd>> ready_sockets_;
|
||||
|
||||
td::int32 max_connections_ = 0;
|
||||
td::string secret_token_;
|
||||
td::Container<Connection> connections_;
|
||||
td::ListNode ready_connections_;
|
||||
td::FloodControlFast active_new_connection_flood_;
|
||||
td::FloodControlFast pending_new_connection_flood_;
|
||||
double last_success_timestamp_ = 0;
|
||||
double last_success_time_ = 0;
|
||||
double wakeup_at_ = 0;
|
||||
bool last_update_was_successful_ = true;
|
||||
|
||||
void relax_wakeup_at(double wakeup_at, const char *source);
|
||||
|
||||
void resolve_ip_address();
|
||||
void on_resolved_ip_address(td::Result<td::IPAddress> r_ip_address);
|
||||
|
||||
void on_ssl_context_created(td::Result<td::SslCtx> r_ssl_ctx);
|
||||
|
||||
td::Status create_webhook_error(td::Slice error_message, td::Status &&result, bool is_public);
|
||||
|
||||
td::Result<td::SslStream> create_ssl_stream();
|
||||
td::Status create_connection() TD_WARN_UNUSED_RESULT;
|
||||
td::Status create_connection(td::SocketFd fd) TD_WARN_UNUSED_RESULT;
|
||||
void on_socket_ready_async(td::Result<td::SocketFd> r_fd, td::int64 id);
|
||||
td::Status create_connection(td::BufferedFd<td::SocketFd> fd) TD_WARN_UNUSED_RESULT;
|
||||
void on_socket_ready_async(td::Result<td::BufferedFd<td::SocketFd>> r_fd, td::int64 id);
|
||||
|
||||
void create_new_connections();
|
||||
|
||||
|
|
@ -191,26 +202,26 @@ class WebhookActor : public td::HttpOutboundConnection::Callback {
|
|||
td::Status send_update() TD_WARN_UNUSED_RESULT;
|
||||
void send_updates();
|
||||
|
||||
void loop() override;
|
||||
void handle(td::unique_ptr<td::HttpQuery> response) override;
|
||||
void loop() final;
|
||||
void handle(td::unique_ptr<td::HttpQuery> response) final;
|
||||
|
||||
void hangup_shared() override;
|
||||
void hangup_shared() final;
|
||||
|
||||
void hangup() override;
|
||||
void hangup() final;
|
||||
|
||||
void tear_down() override;
|
||||
void tear_down() final;
|
||||
|
||||
void start_up() override;
|
||||
void start_up() final;
|
||||
|
||||
bool check_ip_address(const td::IPAddress &addr) const;
|
||||
td::Status check_ip_address(const td::IPAddress &addr) const;
|
||||
|
||||
void on_error(td::Status status);
|
||||
void on_connection_error(td::Status error) override;
|
||||
void on_connection_error(td::Status error) final;
|
||||
void on_webhook_error(td::Slice error);
|
||||
void on_webhook_verified();
|
||||
};
|
||||
|
||||
class JsonUpdate : public td::Jsonable {
|
||||
class JsonUpdate final : public td::Jsonable {
|
||||
public:
|
||||
JsonUpdate(td::int32 id, td::Slice update) : id_(id), update_(update) {
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
|
@ -9,43 +9,42 @@
|
|||
#include "telegram-bot-api/HttpConnection.h"
|
||||
#include "telegram-bot-api/HttpServer.h"
|
||||
#include "telegram-bot-api/HttpStatConnection.h"
|
||||
#include "telegram-bot-api/Query.h"
|
||||
#include "telegram-bot-api/Stats.h"
|
||||
|
||||
#include "td/telegram/ClientActor.h"
|
||||
#include "telegram-bot-api/Watchdog.h"
|
||||
|
||||
#include "td/db/binlog/Binlog.h"
|
||||
#include "td/db/TQueue.h"
|
||||
|
||||
#include "td/net/GetHostByNameActor.h"
|
||||
#include "td/net/HttpInboundConnection.h"
|
||||
|
||||
#include "td/actor/actor.h"
|
||||
#include "td/actor/PromiseFuture.h"
|
||||
#include "td/actor/ConcurrentScheduler.h"
|
||||
|
||||
#include "td/utils/buffer.h"
|
||||
#include "td/utils/AsyncFileLog.h"
|
||||
#include "td/utils/CombinedLog.h"
|
||||
#include "td/utils/common.h"
|
||||
#include "td/utils/crypto.h"
|
||||
#include "td/utils/ExitGuard.h"
|
||||
#include "td/utils/FileLog.h"
|
||||
#include "td/utils/format.h"
|
||||
//#include "td/utils/GitInfo.h"
|
||||
#include "td/utils/logging.h"
|
||||
#include "td/utils/MemoryLog.h"
|
||||
#include "td/utils/misc.h"
|
||||
#include "td/utils/OptionParser.h"
|
||||
#include "td/utils/PathView.h"
|
||||
#include "td/utils/port/detail/ThreadIdGuard.h"
|
||||
#include "td/utils/port/IPAddress.h"
|
||||
#include "td/utils/port/path.h"
|
||||
#include "td/utils/port/rlimit.h"
|
||||
#include "td/utils/port/signals.h"
|
||||
#include "td/utils/port/stacktrace.h"
|
||||
#include "td/utils/port/thread.h"
|
||||
#include "td/utils/port/user.h"
|
||||
#include "td/utils/Promise.h"
|
||||
#include "td/utils/Slice.h"
|
||||
#include "td/utils/SliceBuilder.h"
|
||||
#include "td/utils/Status.h"
|
||||
#include "td/utils/Time.h"
|
||||
|
||||
#include "memprof/memprof.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
|
|
@ -53,10 +52,10 @@
|
|||
|
||||
namespace telegram_bot_api {
|
||||
|
||||
static std::atomic_flag need_rotate_log;
|
||||
static std::atomic_flag need_reopen_log;
|
||||
|
||||
static void rotate_log_signal_handler(int sig) {
|
||||
need_rotate_log.clear();
|
||||
static void after_log_rotation_signal_handler(int sig) {
|
||||
need_reopen_log.clear();
|
||||
}
|
||||
|
||||
static std::atomic_flag need_quit;
|
||||
|
|
@ -68,21 +67,50 @@ static void quit_signal_handler(int sig) {
|
|||
static td::MemoryLog<1 << 20> memory_log;
|
||||
|
||||
void print_log() {
|
||||
td::LogGuard log_guard;
|
||||
auto buf = memory_log.get_buffer();
|
||||
auto pos = memory_log.get_pos();
|
||||
size_t tail_length = buf.size() - pos;
|
||||
while (tail_length > 0 && buf[pos + tail_length - 1] == ' ') {
|
||||
tail_length--;
|
||||
}
|
||||
if (tail_length + 100 >= buf.size() - pos) {
|
||||
tail_length = buf.size() - pos;
|
||||
}
|
||||
td::signal_safe_write("------- Log dump -------\n");
|
||||
td::signal_safe_write(buf.substr(pos), false);
|
||||
td::signal_safe_write(buf.substr(pos, tail_length), false);
|
||||
td::signal_safe_write(buf.substr(0, pos), false);
|
||||
td::signal_safe_write("\n", false);
|
||||
td::signal_safe_write("------------------------\n");
|
||||
}
|
||||
|
||||
static std::atomic_bool has_failed{false};
|
||||
|
||||
static std::atomic_flag need_dump_statistics;
|
||||
|
||||
static void dump_stacktrace_signal_handler(int sig) {
|
||||
if (has_failed) {
|
||||
return;
|
||||
}
|
||||
td::LogGuard log_guard;
|
||||
if (LOG_TAG != nullptr && *LOG_TAG) {
|
||||
td::signal_safe_write(td::Slice(LOG_TAG));
|
||||
td::signal_safe_write(td::Slice("\n"), false);
|
||||
}
|
||||
td::Stacktrace::print_to_stderr();
|
||||
need_dump_statistics.clear();
|
||||
}
|
||||
|
||||
static void fail_signal_handler(int sig) {
|
||||
td::signal_safe_write_signal_number(sig);
|
||||
td::Stacktrace::PrintOptions options;
|
||||
options.use_gdb = true;
|
||||
td::Stacktrace::print_to_stderr(options);
|
||||
has_failed = true;
|
||||
print_log();
|
||||
{
|
||||
td::LogGuard log_guard;
|
||||
td::signal_safe_write_signal_number(sig);
|
||||
td::Stacktrace::PrintOptions options;
|
||||
options.use_gdb = true;
|
||||
td::Stacktrace::print_to_stderr(options);
|
||||
}
|
||||
_Exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
|
|
@ -95,6 +123,9 @@ static void change_verbosity_level_signal_handler(int sig) {
|
|||
static std::atomic_flag need_dump_log;
|
||||
|
||||
static void dump_log_signal_handler(int sig) {
|
||||
if (has_failed) {
|
||||
return;
|
||||
}
|
||||
need_dump_log.clear();
|
||||
}
|
||||
|
||||
|
|
@ -106,16 +137,18 @@ static void sigsegv_signal_handler(int signum, void *addr) {
|
|||
int main(int argc, char *argv[]) {
|
||||
SET_VERBOSITY_LEVEL(VERBOSITY_NAME(FATAL));
|
||||
td::ExitGuard exit_guard;
|
||||
td::detail::ThreadIdGuard thread_id_guard;
|
||||
|
||||
need_rotate_log.test_and_set();
|
||||
need_reopen_log.test_and_set();
|
||||
need_quit.test_and_set();
|
||||
need_change_verbosity_level.test_and_set();
|
||||
need_dump_statistics.test_and_set();
|
||||
need_dump_log.test_and_set();
|
||||
|
||||
td::Stacktrace::init();
|
||||
|
||||
td::setup_signals_alt_stack().ensure();
|
||||
td::set_signal_handler(td::SignalType::User, rotate_log_signal_handler).ensure();
|
||||
td::set_signal_handler(td::SignalType::User, after_log_rotation_signal_handler).ensure();
|
||||
td::ignore_signal(td::SignalType::HangUp).ensure();
|
||||
td::ignore_signal(td::SignalType::Pipe).ensure();
|
||||
td::set_signal_handler(td::SignalType::Quit, quit_signal_handler).ensure();
|
||||
|
|
@ -123,31 +156,39 @@ int main(int argc, char *argv[]) {
|
|||
td::set_signal_handler(td::SignalType::Other, fail_signal_handler).ensure();
|
||||
td::set_extended_signal_handler(td::SignalType::Error, sigsegv_signal_handler).ensure();
|
||||
|
||||
td::set_runtime_signal_handler(0, change_verbosity_level_signal_handler).ensure();
|
||||
td::set_runtime_signal_handler(1, dump_log_signal_handler).ensure();
|
||||
td::set_real_time_signal_handler(0, change_verbosity_level_signal_handler).ensure();
|
||||
td::set_real_time_signal_handler(1, dump_log_signal_handler).ensure();
|
||||
td::set_real_time_signal_handler(2, dump_stacktrace_signal_handler).ensure();
|
||||
|
||||
td::init_openssl_threads();
|
||||
|
||||
auto start_time = td::Time::now();
|
||||
auto shared_data = std::make_shared<SharedData>();
|
||||
auto parameters = std::make_unique<ClientParameters>();
|
||||
parameters->version_ = "7.1";
|
||||
parameters->shared_data_ = shared_data;
|
||||
parameters->start_timestamp_ = start_time;
|
||||
parameters->start_time_ = start_time;
|
||||
auto net_query_stats = td::create_net_query_stats();
|
||||
parameters->net_query_stats_ = net_query_stats;
|
||||
|
||||
td::OptionParser options;
|
||||
bool need_print_usage = false;
|
||||
bool need_print_version = false;
|
||||
int http_port = 8081;
|
||||
int http_stat_port = 0;
|
||||
td::string http_ip_address = "0.0.0.0";
|
||||
td::string http_stat_ip_address = "0.0.0.0";
|
||||
td::string log_file_path;
|
||||
int verbosity_level = 0;
|
||||
int default_verbosity_level = 0;
|
||||
int memory_verbosity_level = VERBOSITY_NAME(INFO);
|
||||
td::int64 log_max_file_size = 2000000000;
|
||||
td::string working_directory;
|
||||
td::string working_directory = PSTRING() << "." << TD_DIR_SLASH;
|
||||
td::string temporary_directory;
|
||||
td::string username;
|
||||
td::string groupname;
|
||||
td::uint64 max_connections = 0;
|
||||
td::uint64 cpu_affinity = 0;
|
||||
td::uint64 main_thread_affinity = 0;
|
||||
ClientManager::TokenRange token_range{0, 1};
|
||||
|
||||
parameters->api_id_ = [](auto x) -> td::int32 {
|
||||
|
|
@ -156,16 +197,17 @@ int main(int argc, char *argv[]) {
|
|||
}
|
||||
return 0;
|
||||
}(std::getenv("TELEGRAM_API_ID"));
|
||||
parameters->api_hash_ = [](auto x) -> std::string {
|
||||
parameters->api_hash_ = [](auto x) -> td::string {
|
||||
if (x) {
|
||||
return x;
|
||||
}
|
||||
return std::string();
|
||||
return td::string();
|
||||
}(std::getenv("TELEGRAM_API_HASH"));
|
||||
|
||||
options.set_usage(td::Slice(argv[0]), "--api_id=<arg> --api-hash=<arg> [--local] [OPTION]...");
|
||||
options.set_usage(td::Slice(argv[0]), "--api-id=<arg> --api-hash=<arg> [--local] [OPTION]...");
|
||||
options.set_description("Telegram Bot API server");
|
||||
options.add_option('h', "help", "display this help text and exit", [&] { need_print_usage = true; });
|
||||
options.add_option('\0', "version", "display version number and exit", [&] { need_print_version = true; });
|
||||
options.add_option('\0', "local", "allow the Bot API server to serve local requests",
|
||||
[&] { parameters->local_mode_ = true; });
|
||||
options.add_checked_option(
|
||||
|
|
@ -192,16 +234,38 @@ int main(int argc, char *argv[]) {
|
|||
std::tie(rem, mod) = td::split(rem_mod, '/');
|
||||
TRY_RESULT(rem_i, td::to_integer_safe<td::uint64>(rem));
|
||||
TRY_RESULT(mod_i, td::to_integer_safe<td::uint64>(mod));
|
||||
if (rem_i >= mod_i) {
|
||||
return td::Status::Error("Wrong argument specified: ensure that remainder < modulo");
|
||||
}
|
||||
token_range = {rem_i, mod_i};
|
||||
return td::Status::OK();
|
||||
});
|
||||
options.add_checked_option('\0', "max-webhook-connections",
|
||||
"default value of the maximum webhook connections per bot",
|
||||
td::OptionParser::parse_integer(parameters->default_max_webhook_connections_));
|
||||
options.add_checked_option('\0', "http-ip-address",
|
||||
"local IP address, HTTP connections to which will be accepted. By default, connections to "
|
||||
"any local IPv4 address are accepted",
|
||||
[&](td::Slice ip_address) {
|
||||
TRY_STATUS(td::IPAddress::get_ip_address(ip_address.str()));
|
||||
http_ip_address = ip_address.str();
|
||||
return td::Status::OK();
|
||||
});
|
||||
options.add_checked_option('\0', "http-stat-ip-address",
|
||||
"local IP address, HTTP statistics connections to which will be accepted. By default, "
|
||||
"statistics connections to any local IPv4 address are accepted",
|
||||
[&](td::Slice ip_address) {
|
||||
TRY_STATUS(td::IPAddress::get_ip_address(ip_address.str()));
|
||||
http_stat_ip_address = ip_address.str();
|
||||
return td::Status::OK();
|
||||
});
|
||||
|
||||
options.add_option('l', "log", "path to the file where the log will be written",
|
||||
td::OptionParser::parse_string(log_file_path));
|
||||
options.add_checked_option('v', "verbosity", "log verbosity level", td::OptionParser::parse_integer(verbosity_level));
|
||||
options.add_checked_option('v', "verbosity", "log verbosity level",
|
||||
td::OptionParser::parse_integer(default_verbosity_level));
|
||||
options.add_checked_option('\0', "memory-verbosity", "memory log verbosity level; defaults to 3",
|
||||
td::OptionParser::parse_integer(memory_verbosity_level));
|
||||
options.add_checked_option(
|
||||
'\0', "log-max-file-size",
|
||||
PSLICE() << "maximum size of the log file in bytes before it will be auto-rotated (default is "
|
||||
|
|
@ -212,17 +276,28 @@ int main(int argc, char *argv[]) {
|
|||
options.add_option('g', "groupname", "effective group name to switch to", td::OptionParser::parse_string(groupname));
|
||||
options.add_checked_option('c', "max-connections", "maximum number of open file descriptors",
|
||||
td::OptionParser::parse_integer(max_connections));
|
||||
|
||||
#if TD_HAVE_THREAD_AFFINITY
|
||||
options.add_checked_option('\0', "cpu-affinity", "CPU affinity as 64-bit mask (defaults to all available CPUs)",
|
||||
td::OptionParser::parse_integer(cpu_affinity));
|
||||
options.add_checked_option(
|
||||
'\0', "proxy", PSLICE() << "HTTP proxy server for outgoing webhook requests in the format http://host:port",
|
||||
[&](td::Slice address) {
|
||||
if (td::begins_with(address, "http://")) {
|
||||
address.remove_prefix(7);
|
||||
} else if (td::begins_with(address, "https://")) {
|
||||
address.remove_prefix(8);
|
||||
}
|
||||
return parameters->webhook_proxy_ip_address_.init_host_port(address.str());
|
||||
});
|
||||
'\0', "main-thread-affinity",
|
||||
"CPU affinity of the main thread as 64-bit mask (defaults to the value of the option --cpu-affinity)",
|
||||
td::OptionParser::parse_integer(main_thread_affinity));
|
||||
#else
|
||||
(void)cpu_affinity;
|
||||
(void)main_thread_affinity;
|
||||
#endif
|
||||
|
||||
options.add_checked_option('\0', "proxy",
|
||||
"HTTP proxy server for outgoing webhook requests in the format http://host:port",
|
||||
[&](td::Slice address) {
|
||||
if (td::begins_with(address, "http://")) {
|
||||
address.remove_prefix(7);
|
||||
} else if (td::begins_with(address, "https://")) {
|
||||
address.remove_prefix(8);
|
||||
}
|
||||
return parameters->webhook_proxy_ip_address_.init_host_port(address.str());
|
||||
});
|
||||
options.add_check([&] {
|
||||
if (parameters->api_id_ <= 0 || parameters->api_hash_.empty()) {
|
||||
return td::Status::Error("You must provide valid api-id and api-hash obtained at https://my.telegram.org");
|
||||
|
|
@ -230,84 +305,60 @@ int main(int argc, char *argv[]) {
|
|||
return td::Status::OK();
|
||||
});
|
||||
options.add_check([&] {
|
||||
if (verbosity_level < 0) {
|
||||
if (default_verbosity_level < 0) {
|
||||
return td::Status::Error("Wrong verbosity level specified");
|
||||
}
|
||||
return td::Status::OK();
|
||||
});
|
||||
options.add_check([&] {
|
||||
if (memory_verbosity_level < 0) {
|
||||
return td::Status::Error("Wrong memory verbosity level specified");
|
||||
}
|
||||
return td::Status::OK();
|
||||
});
|
||||
auto r_non_options = options.run(argc, argv, 0);
|
||||
if (need_print_usage) {
|
||||
LOG(PLAIN) << options;
|
||||
return 0;
|
||||
}
|
||||
if (need_print_version) {
|
||||
LOG(PLAIN) << "Bot API " << parameters->version_;
|
||||
return 0;
|
||||
}
|
||||
if (r_non_options.is_error()) {
|
||||
LOG(PLAIN) << argv[0] << ": " << r_non_options.error();
|
||||
LOG(PLAIN) << argv[0] << ": " << r_non_options.error().message();
|
||||
LOG(PLAIN) << options;
|
||||
return 1;
|
||||
}
|
||||
|
||||
class CombineLog : public td::LogInterface {
|
||||
public:
|
||||
void append(td::CSlice slice, int log_level) override {
|
||||
if (first_ && log_level <= first_verbosity_level_) {
|
||||
first_->append(slice, log_level);
|
||||
}
|
||||
if (second_ && log_level <= second_verbosity_level_) {
|
||||
second_->append(slice, log_level);
|
||||
}
|
||||
}
|
||||
|
||||
void set_first(LogInterface *first) {
|
||||
first_ = first;
|
||||
}
|
||||
|
||||
void set_second(LogInterface *second) {
|
||||
second_ = second;
|
||||
}
|
||||
|
||||
void set_first_verbosity_level(int verbosity_level) {
|
||||
first_verbosity_level_ = verbosity_level;
|
||||
}
|
||||
|
||||
void set_second_verbosity_level(int verbosity_level) {
|
||||
second_verbosity_level_ = verbosity_level;
|
||||
}
|
||||
|
||||
void rotate() override {
|
||||
if (first_) {
|
||||
first_->rotate();
|
||||
}
|
||||
if (second_) {
|
||||
second_->rotate();
|
||||
}
|
||||
}
|
||||
|
||||
td::vector<td::string> get_file_paths() override {
|
||||
td::vector<td::string> result;
|
||||
if (first_) {
|
||||
td::append(result, first_->get_file_paths());
|
||||
}
|
||||
if (second_) {
|
||||
td::append(result, second_->get_file_paths());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
LogInterface *first_ = nullptr;
|
||||
int first_verbosity_level_ = VERBOSITY_NAME(FATAL);
|
||||
LogInterface *second_ = nullptr;
|
||||
int second_verbosity_level_ = VERBOSITY_NAME(FATAL);
|
||||
};
|
||||
CombineLog log;
|
||||
td::CombinedLog log;
|
||||
log.set_first(td::default_log_interface);
|
||||
log.set_second(&memory_log);
|
||||
td::log_interface = &log;
|
||||
|
||||
td::FileLog file_log;
|
||||
td::TsLog ts_log(&file_log);
|
||||
td::AsyncFileLog file_log;
|
||||
|
||||
auto init_status = [&] {
|
||||
#if TD_HAVE_THREAD_AFFINITY
|
||||
if (main_thread_affinity == 0) {
|
||||
main_thread_affinity = cpu_affinity;
|
||||
}
|
||||
if (main_thread_affinity != 0) {
|
||||
auto initial_mask = td::thread::get_affinity_mask(td::this_thread::get_id());
|
||||
if (initial_mask == 0) {
|
||||
return td::Status::Error("Failed to get current thread affinity");
|
||||
}
|
||||
if (cpu_affinity != 0) {
|
||||
TRY_STATUS_PREFIX(td::thread::set_affinity_mask(td::this_thread::get_id(), cpu_affinity),
|
||||
"Can't set CPU affinity mask: ");
|
||||
} else {
|
||||
cpu_affinity = initial_mask;
|
||||
}
|
||||
TRY_STATUS_PREFIX(td::thread::set_affinity_mask(td::this_thread::get_id(), main_thread_affinity),
|
||||
"Can't set main thread CPU affinity mask: ");
|
||||
}
|
||||
#endif
|
||||
|
||||
if (max_connections != 0) {
|
||||
TRY_STATUS_PREFIX(td::set_resource_limit(td::ResourceLimitType::NoFile, max_connections),
|
||||
"Can't set file descriptor limit: ");
|
||||
|
|
@ -317,87 +368,147 @@ int main(int argc, char *argv[]) {
|
|||
TRY_STATUS_PREFIX(td::change_user(username, groupname), "Can't change effective user: ");
|
||||
}
|
||||
|
||||
if (!working_directory.empty()) {
|
||||
TRY_STATUS_PREFIX(td::chdir(working_directory), "Can't set working directory: ");
|
||||
{
|
||||
TRY_RESULT_PREFIX_ASSIGN(working_directory, td::realpath(working_directory, true),
|
||||
"Invalid working directory specified: ");
|
||||
if (working_directory.empty()) {
|
||||
return td::Status::Error("Empty path specified as working directory");
|
||||
}
|
||||
if (working_directory.back() != TD_DIR_SLASH) {
|
||||
working_directory += TD_DIR_SLASH;
|
||||
}
|
||||
|
||||
TRY_STATUS_PREFIX(td::mkpath(working_directory, 0750), "Failed to create working directory: ");
|
||||
|
||||
auto r_temp_file = td::mkstemp(working_directory);
|
||||
if (r_temp_file.is_error()) {
|
||||
return td::Status::Error(PSLICE() << "Can't create files in the directory \"" << working_directory
|
||||
<< "\". Use --dir option to specify a writable working directory");
|
||||
}
|
||||
r_temp_file.ok_ref().first.close();
|
||||
td::unlink(r_temp_file.ok().second).ensure();
|
||||
|
||||
auto r_temp_dir = td::mkdtemp(working_directory, "1:a");
|
||||
if (r_temp_dir.is_error()) {
|
||||
parameters->allow_colon_in_filenames_ = false;
|
||||
r_temp_dir = td::mkdtemp(working_directory, "1~a");
|
||||
if (r_temp_dir.is_error()) {
|
||||
return td::Status::Error(PSLICE() << "Can't create directories in the directory \"" << working_directory
|
||||
<< "\". Use --dir option to specify a writable working directory");
|
||||
}
|
||||
}
|
||||
td::rmdir(r_temp_dir.ok()).ensure();
|
||||
}
|
||||
|
||||
if (!temporary_directory.empty()) {
|
||||
if (td::PathView(temporary_directory).is_relative()) {
|
||||
temporary_directory = working_directory + temporary_directory;
|
||||
}
|
||||
TRY_STATUS_PREFIX(td::set_temporary_dir(temporary_directory), "Can't set temporary directory: ");
|
||||
}
|
||||
|
||||
{ // check temporary directory
|
||||
auto temp_dir = td::get_temporary_dir();
|
||||
if (temp_dir.empty()) {
|
||||
return td::Status::Error("Can't find directory for temporary files. Use --temp-dir option to specify it");
|
||||
}
|
||||
|
||||
auto r_temp_file = td::mkstemp(temp_dir);
|
||||
if (r_temp_file.is_error()) {
|
||||
return td::Status::Error(PSLICE()
|
||||
<< "Can't create files in the directory \"" << temp_dir
|
||||
<< "\". Use --temp-dir option to specify another directory for temporary files");
|
||||
}
|
||||
r_temp_file.ok_ref().first.close();
|
||||
td::unlink(r_temp_file.ok().second).ensure();
|
||||
}
|
||||
|
||||
if (!log_file_path.empty()) {
|
||||
if (td::PathView(log_file_path).is_relative()) {
|
||||
log_file_path = working_directory + log_file_path;
|
||||
}
|
||||
TRY_STATUS_PREFIX(file_log.init(log_file_path, log_max_file_size), "Can't open log file: ");
|
||||
log.set_first(&ts_log);
|
||||
log.set_first(&file_log);
|
||||
}
|
||||
|
||||
return td::Status::OK();
|
||||
}();
|
||||
if (init_status.is_error()) {
|
||||
LOG(PLAIN) << init_status.error();
|
||||
LOG(PLAIN) << init_status.message();
|
||||
LOG(PLAIN) << options;
|
||||
return 1;
|
||||
}
|
||||
|
||||
parameters->working_directory_ = std::move(working_directory);
|
||||
|
||||
if (parameters->default_max_webhook_connections_ <= 0) {
|
||||
parameters->default_max_webhook_connections_ = parameters->local_mode_ ? 100 : 40;
|
||||
}
|
||||
|
||||
::td::VERBOSITY_NAME(dns_resolver) = VERBOSITY_NAME(WARNING);
|
||||
|
||||
int memory_verbosity_level = td::max(VERBOSITY_NAME(INFO), verbosity_level);
|
||||
SET_VERBOSITY_LEVEL(memory_verbosity_level);
|
||||
log.set_first_verbosity_level(verbosity_level);
|
||||
log.set_second_verbosity_level(memory_verbosity_level);
|
||||
|
||||
auto set_verbosity_level = [&log, memory_verbosity_level](int new_verbosity_level) {
|
||||
SET_VERBOSITY_LEVEL(td::max(memory_verbosity_level, new_verbosity_level));
|
||||
log.set_first_verbosity_level(new_verbosity_level);
|
||||
};
|
||||
set_verbosity_level(default_verbosity_level);
|
||||
|
||||
// LOG(WARNING) << "Bot API server with commit " << td::GitInfo::commit() << ' '
|
||||
// << (td::GitInfo::is_dirty() ? "(dirty)" : "") << " started";
|
||||
LOG(WARNING) << "Bot API server started";
|
||||
LOG(WARNING) << "Bot API " << parameters->version_ << " server started";
|
||||
|
||||
const int threads_n = 5; // +3 for Td, one for slow HTTP connections and one for DNS resolving
|
||||
td::ConcurrentScheduler sched;
|
||||
sched.init(threads_n);
|
||||
td::ConcurrentScheduler sched(SharedData::get_thread_count() - 1, cpu_affinity);
|
||||
|
||||
td::GetHostByNameActor::Options get_host_by_name_options;
|
||||
get_host_by_name_options.scheduler_id = threads_n;
|
||||
get_host_by_name_options.scheduler_id = SharedData::get_dns_resolver_scheduler_id();
|
||||
parameters->get_host_by_name_actor_id_ =
|
||||
sched.create_actor_unsafe<td::GetHostByNameActor>(0, "GetHostByName", std::move(get_host_by_name_options))
|
||||
.release();
|
||||
|
||||
auto client_manager =
|
||||
sched.create_actor_unsafe<ClientManager>(0, "ClientManager", std::move(parameters), token_range).release();
|
||||
auto client_manager = sched
|
||||
.create_actor_unsafe<ClientManager>(SharedData::get_client_scheduler_id(), "ClientManager",
|
||||
std::move(parameters), token_range)
|
||||
.release();
|
||||
|
||||
sched
|
||||
.create_actor_unsafe<HttpServer>(
|
||||
0, "HttpServer", http_port,
|
||||
SharedData::get_client_scheduler_id(), "HttpServer", http_ip_address, http_port,
|
||||
[client_manager, shared_data] {
|
||||
return td::ActorOwn<td::HttpInboundConnection::Callback>(
|
||||
td::create_actor<HttpConnection>("HttpConnection", client_manager, shared_data));
|
||||
})
|
||||
.release();
|
||||
|
||||
if (http_stat_port != 0) {
|
||||
sched
|
||||
.create_actor_unsafe<HttpServer>(
|
||||
0, "HttpStatsServer", http_stat_port,
|
||||
SharedData::get_client_scheduler_id(), "HttpStatsServer", http_stat_ip_address, http_stat_port,
|
||||
[client_manager] {
|
||||
return td::ActorOwn<td::HttpInboundConnection::Callback>(
|
||||
td::create_actor<HttpStatConnection>("HttpStatConnection", client_manager));
|
||||
})
|
||||
.release();
|
||||
}
|
||||
|
||||
constexpr double WATCHDOG_TIMEOUT = 0.25;
|
||||
auto watchdog_id = sched.create_actor_unsafe<Watchdog>(SharedData::get_watchdog_scheduler_id(), "Watchdog",
|
||||
td::this_thread::get_id(), WATCHDOG_TIMEOUT);
|
||||
|
||||
sched.start();
|
||||
|
||||
double last_cron_time = start_time;
|
||||
double next_watchdog_kick_time = start_time;
|
||||
double next_cron_time = start_time;
|
||||
double last_dump_time = start_time - 1000.0;
|
||||
double last_tqueue_gc_time = start_time - 1000.0;
|
||||
td::int64 tqueue_deleted_events = 0;
|
||||
td::int64 last_tqueue_deleted_events = 0;
|
||||
bool close_flag = false;
|
||||
std::atomic_bool can_quit{false};
|
||||
ServerCpuStat::instance(); // create ServerCpuStat instance
|
||||
while (true) {
|
||||
sched.run_main(1);
|
||||
sched.run_main(td::min(next_cron_time, next_watchdog_kick_time) - td::Time::now());
|
||||
|
||||
if (!need_rotate_log.test_and_set()) {
|
||||
td::log_interface->rotate();
|
||||
if (!need_reopen_log.test_and_set()) {
|
||||
td::log_interface->after_rotation();
|
||||
}
|
||||
|
||||
if (!need_quit.test_and_set()) {
|
||||
|
|
@ -406,9 +517,10 @@ int main(int argc, char *argv[]) {
|
|||
std::_Exit(0);
|
||||
}
|
||||
|
||||
LOG(WARNING) << "Stopping engine by a signal";
|
||||
LOG(WARNING) << "Stopping engine with uptime " << (td::Time::now() - start_time) << " seconds by a signal";
|
||||
close_flag = true;
|
||||
auto guard = sched.get_main_guard();
|
||||
watchdog_id.reset();
|
||||
send_closure(client_manager, &ClientManager::close, td::PromiseCreator::lambda([&can_quit](td::Unit) {
|
||||
can_quit.store(true);
|
||||
td::Scheduler::instance()->yield();
|
||||
|
|
@ -419,96 +531,53 @@ int main(int argc, char *argv[]) {
|
|||
}
|
||||
|
||||
if (!need_change_verbosity_level.test_and_set()) {
|
||||
auto current_global_verbosity_level = GET_VERBOSITY_LEVEL();
|
||||
SET_VERBOSITY_LEVEL(256 - current_global_verbosity_level);
|
||||
verbosity_level = 256 - verbosity_level;
|
||||
log.set_first_verbosity_level(verbosity_level);
|
||||
if (log.get_first_verbosity_level() == default_verbosity_level) {
|
||||
// increase default log verbosity level
|
||||
set_verbosity_level(100);
|
||||
} else {
|
||||
// return back verbosity level
|
||||
set_verbosity_level(default_verbosity_level);
|
||||
}
|
||||
}
|
||||
|
||||
auto next_verbosity_level = shared_data->next_verbosity_level_.exchange(-1);
|
||||
if (next_verbosity_level != -1) {
|
||||
verbosity_level = next_verbosity_level;
|
||||
memory_verbosity_level = td::max(VERBOSITY_NAME(INFO), verbosity_level);
|
||||
SET_VERBOSITY_LEVEL(memory_verbosity_level);
|
||||
|
||||
log.set_first_verbosity_level(verbosity_level);
|
||||
log.set_second_verbosity_level(memory_verbosity_level);
|
||||
set_verbosity_level(next_verbosity_level);
|
||||
}
|
||||
|
||||
if (!need_dump_log.test_and_set()) {
|
||||
print_log();
|
||||
need_dump_statistics.clear();
|
||||
}
|
||||
|
||||
double now = td::Time::now();
|
||||
if (now > last_cron_time + 1.0) {
|
||||
last_cron_time = now;
|
||||
ServerCpuStat::update(now);
|
||||
}
|
||||
|
||||
if (now > last_tqueue_gc_time + 60.0) {
|
||||
auto unix_time = shared_data->get_unix_time(now);
|
||||
LOG(INFO) << "Run TQueue GC at " << unix_time;
|
||||
last_tqueue_gc_time = now;
|
||||
if (now >= next_cron_time) {
|
||||
if (now >= next_cron_time + 1.0) {
|
||||
next_cron_time = now;
|
||||
}
|
||||
next_cron_time += 1.0;
|
||||
auto guard = sched.get_main_guard();
|
||||
auto deleted_events = shared_data->tqueue_->run_gc(unix_time);
|
||||
LOG(INFO) << "TQueue GC deleted " << deleted_events << " events";
|
||||
|
||||
tqueue_deleted_events += deleted_events;
|
||||
if (tqueue_deleted_events > last_tqueue_deleted_events + 10000) {
|
||||
LOG(WARNING) << "TQueue GC already deleted " << tqueue_deleted_events << " events since the start";
|
||||
last_tqueue_deleted_events = tqueue_deleted_events;
|
||||
}
|
||||
td::Scheduler::instance()->run_on_scheduler(SharedData::get_statistics_thread_id(),
|
||||
[](td::Unit) { ServerCpuStat::update(td::Time::now()); });
|
||||
}
|
||||
|
||||
if (now > last_dump_time + 300.0) {
|
||||
if (now >= start_time + 600) {
|
||||
auto guard = sched.get_main_guard();
|
||||
send_closure(watchdog_id, &Watchdog::kick);
|
||||
next_watchdog_kick_time = now + WATCHDOG_TIMEOUT / 10;
|
||||
}
|
||||
|
||||
if (!need_dump_statistics.test_and_set() || now > last_dump_time + 300.0) {
|
||||
last_dump_time = now;
|
||||
if (is_memprof_on()) {
|
||||
LOG(WARNING) << "Memory dump:";
|
||||
td::vector<AllocInfo> v;
|
||||
dump_alloc([&](const AllocInfo &info) { v.push_back(info); });
|
||||
std::sort(v.begin(), v.end(), [](const AllocInfo &a, const AllocInfo &b) { return a.size > b.size; });
|
||||
size_t total_size = 0;
|
||||
size_t other_size = 0;
|
||||
int count = 0;
|
||||
for (auto &info : v) {
|
||||
if (count++ < 50) {
|
||||
LOG(WARNING) << td::format::as_size(info.size) << td::format::as_array(info.backtrace);
|
||||
} else {
|
||||
other_size += info.size;
|
||||
}
|
||||
total_size += info.size;
|
||||
}
|
||||
LOG(WARNING) << td::tag("other", td::format::as_size(other_size));
|
||||
LOG(WARNING) << td::tag("total", td::format::as_size(total_size));
|
||||
LOG(WARNING) << td::tag("total traces", get_ht_size());
|
||||
LOG(WARNING) << td::tag("fast_backtrace_success_rate", get_fast_backtrace_success_rate());
|
||||
}
|
||||
LOG(WARNING) << td::tag("buffer_mem", td::format::as_size(td::BufferAllocator::get_buffer_mem()));
|
||||
LOG(WARNING) << td::tag("buffer_slice_size", td::format::as_size(td::BufferAllocator::get_buffer_slice_size()));
|
||||
|
||||
auto query_count = shared_data->query_count_.load();
|
||||
LOG(WARNING) << td::tag("pending queries", query_count);
|
||||
|
||||
td::uint64 i = 0;
|
||||
bool was_gap = false;
|
||||
for (auto end = &shared_data->query_list_, cur = end->prev; cur != end; cur = cur->prev, i++) {
|
||||
if (i < 20 || i > query_count - 20 || i % (query_count / 50 + 1) == 0) {
|
||||
if (was_gap) {
|
||||
LOG(WARNING) << "...";
|
||||
was_gap = false;
|
||||
}
|
||||
LOG(WARNING) << static_cast<Query &>(*cur);
|
||||
} else {
|
||||
was_gap = true;
|
||||
}
|
||||
}
|
||||
|
||||
td::dump_pending_network_queries(*net_query_stats);
|
||||
auto guard = sched.get_main_guard();
|
||||
send_closure(client_manager, &ClientManager::dump_statistics);
|
||||
}
|
||||
}
|
||||
|
||||
LOG(WARNING) << "--------------------FINISH ENGINE--------------------";
|
||||
CHECK(net_query_stats.use_count() == 1);
|
||||
if (net_query_stats.use_count() != 1) {
|
||||
LOG(ERROR) << "NetQueryStats have leaked";
|
||||
}
|
||||
net_query_stats = nullptr;
|
||||
sched.finish();
|
||||
SET_VERBOSITY_LEVEL(VERBOSITY_NAME(FATAL));
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue