diff --git a/docs/en/about-upstream.md b/docs/en/about-upstream.md new file mode 100644 index 0000000000..aa75837989 --- /dev/null +++ b/docs/en/about-upstream.md @@ -0,0 +1,410 @@ +# About Upstream + +In nginx, Upstream represents the load balancing configuration of the reverse proxy. Here, we expand the meaning of Upstream so that it has the following characteristics: + +1. Each Upstream is an independent reverse proxy +2. Accessing an Upstream is equivalent to using an appropriate strategy to select one in a group of services/targets/upstream and downstream for access +3. Upstream has load balancing, error handling and service governance capabilities. + +### Advantages of Upstream over domain name DNS resolution + +Both upstream and domain name DNS resolution can configure a group of ip to a host, but + +1. DNS domain name resolution doesn’t address port number. The service DNS domain names with the same IP and different ports cannot be configured together; but it is possible for Upstream. +2. The set of addresses corresponding to DNS domain name resolution must be ip; while the set of addresses corresponding to Upstream can be ip, domain name or unix-domain-socket +3. Normally, DNS domain name resolution will be cached by operating system or DNS server on the network, and the update time is limited by ttl; while Upstream can be updated in real time and take effect in real time +4. The consumption of DNS domain name is much greater than that of Upstream resolution and selection + +### Upstream of Workflow + +This is a local reverse proxy module, and the proxy configuration is effective for both server and client. + +Proxy supports dynamic configuration. Proxy name does not include port, but proxy request supports specified port. + +Each Upstream is configured with its own independent name UpstreamName, and a set of Addresses is added and set. These Addresses can be: + +1. ip4 +2. ip6 +3. Domain name +4. unix-domain-socket + +### Why to replace nginx's Upstream + +#### Upstream working mode of nginx + +1. Supports http/https protocol only + +2. Needs to build a nginx service, start the start process occupies socket and other resources + +3. The request is sent to nginx first, and nginx forwards the request to remote end, which will increase one more network communication overhead + +#### Local Upstream working method of workflow + +1. Protocol irrelevant, you can even access mysql, redis, mongodb, etc. through upstream + +2. You can directly simulate the function of reverse proxy in the process, no need to start other processes or ports + +3. The selection process is basic calculation and table lookup, no additional network communication overhead + +# Use Upstream + +### Common interfaces + +~~~cpp +class UpstreamManager +{ +public: + static int upstream_create_consistent_hash(const std::string& name, + upstream_route_t consitent_hash); + static int upstream_create_weighted_random(const std::string& name, + bool try_another); + static int upstream_create_manual(const std::string& name, + upstream_route_t select, + bool try_another, + upstream_route_t consitent_hash); + static int upstream_delete(const std::string& name); + +public: + static int upstream_add_server(const std::string& name, + const std::string& address); + static int upstream_add_server(const std::string& name, + const std::string& address, + const struct AddressParams *address_params); + static int upstream_remove_server(const std::string& name, + const std::string& address); + ... +} +~~~ + +### Example 1 Random access in multiple targets + +Configure a local reverse proxy to evenly send all the local requests for **my_proxy.name** to 6 target servers + +~~~cpp +UpstreamManager::upstream_create_weighted_random( + "my_proxy.name", + true); // In case of fusing, retry till the available is found or all fuses are blown + +UpstreamManager::upstream_add_server("my_proxy.name", "192.168.2.100:8081"); +UpstreamManager::upstream_add_server("my_proxy.name", "192.168.2.100:8082"); +UpstreamManager::upstream_add_server("my_proxy.name", "192.168.10.10"); +UpstreamManager::upstream_add_server("my_proxy.name", "test.sogou.com:8080"); +UpstreamManager::upstream_add_server("my_proxy.name", "abc.sogou.com"); +UpstreamManager::upstream_add_server("my_proxy.name", "abc.sogou.com"); +UpstreamManager::upstream_add_server("my_proxy.name", "/dev/unix_domain_scoket_sample"); + +auto *http_task = WFTaskFactory::create_http_task("http://my_proxy.name/somepath?a=10", 0, 0, nullptr); +http_task->start(); +~~~ + +Basic principles + +1. Select a target randomly +2. If try_another is configured as true, one of all surviving targets will be selected randomly +3. Select in the main servers only, the mains and backups of the group where the selected target is located and the backup without group are regarded as valid optional objects + +### Example 2 Random access among multiple targets based on weights + +Configure a local reverse proxy, send all **weighted.random** requests to the 3 target servers based on the weight distribution of 5/20/1 + +~~~cpp +UpstreamManager::upstream_create_weighted_random( + "weighted.random", + false); // If you don’t retry in case of fusing, the request will surely fail + +AddressParams address_params = ADDRESS_PARAMS_DEFAULT; +address_params.weight = 5; //weight is 5 +UpstreamManager::upstream_add_server("weighted.random", "192.168.2.100:8081", &address_params); // weight is 5 +address_params.weight = 20; // weight is 20 +UpstreamManager::upstream_add_server("weighted.random", "192.168.2.100:8082", &address_params); // weight is 20 +UpstreamManager::upstream_add_server("weighted.random", "abc.sogou.com"); // weight is 1 + +auto *http_task = WFTaskFactory::create_http_task("http://weighted.random:9090", 0, 0, nullptr); +http_task->start(); +~~~ + +Basic principles + +1. According to the weight distribution, randomly select a target, the greater the weight is, the greater the probability is +2. If try_another is configured as true, one of all surviving targets will be selected randomly as per weights. +3. Select in the main servers only, the main and backup of the group where the selected target is located and the backup without group are regarded as valid optional objects + +### Example 3 Access among multiple targets based on the framework's default consistent hash + +~~~cpp +UpstreamManager::upstream_create_consistent_hash( + "abc.local", + nullptr); // nullptr represents using the default consistent hash function of the framework + +UpstreamManager::upstream_add_server("abc.local", "192.168.2.100:8081"); +UpstreamManager::upstream_add_server("abc.local", "192.168.2.100:8082"); +UpstreamManager::upstream_add_server("abc.local", "192.168.10.10"); +UpstreamManager::upstream_add_server("abc.local", "test.sogou.com:8080"); +UpstreamManager::upstream_add_server("abc.local", "abc.sogou.com"); + +auto *http_task = WFTaskFactory::create_http_task("http://abc.local/service/method", 0, 0, nullptr); +http_task->start(); +~~~ + +Basic principles + +1. Each main server is regarded as 16 virtual nodes +2. The framework will use std::hash to calculate the address + virtual index of all nodes as the node value of the consistent hash +3. The framework will use std::hash to calculate path + query + fragment as a consistent hash data value +4. Choose the value nearest to the surviving node as the target each time +5. For each main, as long as there is a main in surviving group, or there is a backup in surviving group, or there is a surviving no group backup, it is regarded as surviving + +### Example 4 User-defined consistent hash function + +~~~cpp +UpstreamManager::upstream_create_consistent_hash( + "abc.local", + [](const char *path, const char *query, const char *fragment) -> unsigned int { + unsigned int hash = 0; + + while (*path) + hash = (hash * 131) + (*path++); + + while (*query) + hash = (hash * 131) + (*query++); + + while (*fragment) + hash = (hash * 131) + (*fragment++); + + return hash; + }); + +UpstreamManager::upstream_add_server("abc.local", "192.168.2.100:8081"); +UpstreamManager::upstream_add_server("abc.local", "192.168.2.100:8082"); +UpstreamManager::upstream_add_server("abc.local", "192.168.10.10"); +UpstreamManager::upstream_add_server("abc.local", "test.sogou.com:8080"); +UpstreamManager::upstream_add_server("abc.local", "abc.sogou.com"); + +auto *http_task = WFTaskFactory::create_http_task("http://abc.local/sompath?a=1#flag100", 0, 0, nullptr); +http_task->start(); +~~~ + +Basic principles + +1. The framework will use a user-defined consistent hash function as the data value +2. The rest is the same as the above principles + +### Example 5 User-defined selection strategy + +~~~cpp +UpstreamManager::upstream_create_manual( + "xyz.cdn", + [](const char *path, const char *query, const char *fragment) -> unsigned int { + return atoi(fragment); + }, + true, // If a blown target is selected, a second selection will be made + nullptr); // nullptr represents using the default consistent hash function of the framework in the second selection + +UpstreamManager::upstream_add_server("xyz.cdn", "192.168.2.100:8081"); +UpstreamManager::upstream_add_server("xyz.cdn", "192.168.2.100:8082"); +UpstreamManager::upstream_add_server("xyz.cdn", "192.168.10.10"); +UpstreamManager::upstream_add_server("xyz.cdn", "test.sogou.com:8080"); +UpstreamManager::upstream_add_server("xyz.cdn", "abc.sogou.com"); + +auto *http_task = WFTaskFactory::create_http_task("http://xyz.cdn/sompath?key=somename#3", 0, 0, nullptr); +http_task->start(); +~~~ + +Basic principles + +1. The framework first determines the selection in the main server list according to the normal selection function provided by the user and then get the modulo +2. For each main server, as long as there is a main server in surviving group, or there is a backup in surviving group, or there is a surviving no group backup, it is regarded as surviving +3. If the selected target no longer survives and try_another is set as true, a second selection will be made using consistent hash function +4. If the second selection is triggered, the consistent hash will ensure that a survival target will be selected, unless all machines are blown + +### Example 6 Simple main-backup mode +~~~cpp +UpstreamManager::upstream_create_weighted_random( + "simple.name", + true);//One main, one backup, nothing is different in this item + +AddressParams address_params = ADDRESS_PARAMS_DEFAULT; +address_params.server_type = 0; /* 1 for main server */ +UpstreamManager::upstream_add_server("simple.name", "main01.test.ted.bj.sogou", &address_params); // main +address_params.server_type = 1; /* 0 for backup server */ +UpstreamManager::upstream_add_server("simple.name", "backup01.test.ted.gd.sogou", &address_params); //backup + +auto *http_task = WFTaskFactory::create_http_task("http://simple.name/request", 0, 0, nullptr); +auto *redis_task = WFTaskFactory::create_redis_task("redis://simple.name/2", 0, nullptr); +redis_task->get_req()->set_query("MGET", {"key1", "key2", "key3", "key4"}); +(*http_task * redis_task).start(); +~~~ + +Basic principles +1. The main-backup mode does not conflict with any of the modes shown above, and it can take effect at the same time +2. The number of main/backup is independent of each other and there is no limit. All main servers are coequal to each other, and all backup servers are coequal to each others, but main and backup are not coequal to each other. +3. As long as a main server is alive, the request will always use a main server. +4. If all main servers are blown, backup server will take over the request as a substitute target until any main server works well again +5. In every strategy, surviving backup can be used as the basis for the survival of main + +### Example 7 Main-backup + consistent hash + grouping +~~~cpp +UpstreamManager::upstream_create_consistent_hash( + "abc.local", + nullptr);//nullptr represents using the default consistent hash function of the framework + +AddressParams address_params = ADDRESS_PARAMS_DEFAULT; +address_params.server_type = 0; +address_params.group_id = 1001; +UpstreamManager::upstream_add_server("abc.local", "192.168.2.100:8081", &address_params);//main in group 1001 +address_params.server_type = 1; +address_params.group_id = 1001; +UpstreamManager::upstream_add_server("abc.local", "192.168.2.100:8082", &address_params);//backup for group 1001 +address_params.server_type = 0; +address_params.group_id = 1002; +UpstreamManager::upstream_add_server("abc.local", "backup01.test.ted.bj.sogou", &address_params);//main in group 1002 +address_params.server_type = 1; +address_params.group_id = 1002; +UpstreamManager::upstream_add_server("abc.local", "backup01.test.ted.gd.sogou", &address_params);//backup for group 1002 +address_params.server_type = 1; +address_params.group_id = -1; +UpstreamManager::upstream_add_server("abc.local", "test.sogou.com:8080", &address_params);//backup with no group mean backup for all groups and no group +UpstreamManager::upstream_add_server("abc.local", "abc.sogou.com");//main, no group + +auto *http_task = WFTaskFactory::create_http_task("http://abc.local/service/method", 0, 0, nullptr); +http_task->start(); +~~~ + +Basic principles + +1. Group number -1 means no group, this kind of target does not belong to any group +2. The main servers without a group are coequal to each other, and they can even be regarded as one group. But they are isolated from the other main servers with a group +3. A backup without a group can serve as a backup for any group target of Global/any target without a group +4. The group number can identify which main and backup are working together +5. The backups of different groups are isolated from each other, and they serve the main servers of their own group only +6. Add the default group number -1 of the target, and the type is main + +# Upstream selection strategy + +When the URIHost of the url that initiates the request is filled with UpstreamName, it is regarded as a request to the Upstream corresponding to the name, and then it will be selected from the set of Addresses recorded by the Upstream: + +1. Weight random strategy: selection randomly according to weight +2. Consistent hashing strategy: The framework uses a standard consistent hashing algorithm, and users can define the consistent hash function consistent_hash for the requested uri +3. Manual strategy: make definite selection according to the select function that user provided for the requested uri, if the blown target is selected: **a.** If try_another is false, this request will return to failure **b.** If try_another is true, the framework uses standard consistent hash algorithm to make a second selection, and the user can define the consistent hash function consistent_hash for the requested uri +4. Main-backup strategy: According to the priority of main first, backup next, select a main server as long as it can be used. This strategy can take effect concurrently with any of [1], [2], and [3], and they influence each other. + +Round-robin/weighted-round-robin: regarded as equivalent to [1], not available for now + +The framework recommends common users to use strategy [2], which can ensure that the cluster has good fault tolerance and scalability + +For complex scenarios, advanced users can use strategy [3] to customize complex selection logic + +# Address attribute +~~~cpp +struct EndpointParams +{ + size_t max_connections; + int connect_timeout; + int response_timeout; + int ssl_connect_timeout; +}; + +static constexpr struct EndpointParams ENDPOINT_PARAMS_DEFAULT = +{ + .max_connections = 200, + .connect_timeout = 10 * 1000, + .response_timeout = 10 * 1000, + .ssl_connect_timeout = 10 * 1000, +}; + +struct AddressParams +{ + struct EndpointParams endpoint_params; + unsigned int dns_ttl_default; + unsigned int dns_ttl_min; + unsigned int max_fails; + unsigned short weight; + int server_type; /* 0 for main and 1 for backup. */ + int group_id; +}; + +static constexpr struct AddressParams ADDRESS_PARAMS_DEFAULT = +{ + .endpoint_params = ENDPOINT_PARAMS_DEFAULT, + .dns_ttl_default = 12 * 3600, + .dns_ttl_min = 180, + .max_fails = 200, + .weight = 1, // only for main of UPSTREAM_WEIGHTED_RANDOM + .server_type = 0, + .group_id = -1, +}; +~~~ + +Each address can be configured with custom parameters: + + * Max_connections, connect_timeout, response_timeout, ssl_connect_timeout of EndpointParams: connection-related parameters + * dns_ttl_default: The default ttl in the dns cache in seconds, and the default value is 12 hours. The dns cache is for the current process, that is, the process will disappear after exiting, and the configuration is only valid for the current process + * dns_ttl_min: The shortest effective time of dns in seconds, and the default value is 3 minutes. It is used to decide whether to perform dns again when communication fails and retry. + * max_fails: the number of [continuous] failures that triggered fusing (Note: each time the communication is successful, the count will be cleared) + * Weight: weight, the default value is 1, which is only valid for main. It is used for Upstream random strategy selection, the larger the weight is, the easier it is to be selected; this parameter is meaningless under other strategies + * server_type: main/backup configuration, main by default (server_type=0). At any time, the main servers in the same group are always at higher priority than backups + * group_id: basis for grouping, the default value is -1. -1 means no grouping (free). A free backup can be regarded as backup to any main server. Any backup with group is always at higher priority than any free backup. + +# About fuse + +## MTTR +Mean time to repair (MTTR) is the average value of the repair time when the product changes from a fault state to a working state. + +## Service avalanche effect + +Service avalanche effect is a phenomenon in which "service caller failure" (result) is caused by "service provider's failure" (cause), and the unavailability is amplified gradually/level by level + +If it is not controlled effectively, the effect will not converge, but will be amplified geometrically, just like an avalanche, that’s why it is called avalanche effect + +Description of the phenomenon: at first it is just a small service or module abnormality/timeout, causing abnormality/timeout of other downstream dependent services, then causing a chain reaction, eventually leading to paralysis of most or all services + +As the fault is repaired, the effect will disappear, so the duration of the effect is usually equal to MTTR + +## Fuse mechanism + +When the error or abnormal touch of a certain target meets the preset threshold condition, the target is temporarily considered unavailable, and the target is removed, namely fuse is started and enters the fuse period + +After the fuse duration reaches MTTR duration, the fuse is closed, (attempt to) restore the target + +Fuse mechanism strategy can effectively prevent avalanche effect + +## Upstream fuse protection mechanism + +MTTR=30 seconds, which is temporarily not configurable, but we will consider opening it to be configured by users in the future. + +When the number of consecutive failures of a certain Address reaches the set upper limit (200 times by default), this Address will be blown, MTTR=30 seconds. + +During the fusing period, once the Address is selected by the strategy, Upstream will decide whether to try other Addresses and how to try according to the specific configuration + +Please note that if one of the following 1-4 scenarios is met, the communication task will get an error of WFT_ERR_UPSTREAM_UNAVAILABLE = 1004: + + 1. Weight random strategy, all targets are in the fusing period + 2. Consistent hash strategy, all targets are in the fusing period + 3. Manual strategy and try_another==true, all targets are in the fusing period + 4. Manual strategy and try_another==false, and all the following three conditions shall meet at the same time: + + 1). The main selected by the select function is in the fusing period, and all free devices are in the fusing period + + 2). The main is a free main, or other targets in the group where the main is located are all in the fusing period + + 3). All free devices are in the fusing period + +# Upstream port priority + +1. Priority is given to the port number explicitly configured on the Upstream Address + +2. If not, select the port number explicitly configured in the request url + +3. If none, use the default port number of the protocol + +~~~cpp +Configure UpstreamManager::upstream_add_server("my_proxy.name", "192.168.2.100:8081"); +Request http://my_proxy.name:456/test.html => http://192.168.2.100:8081/test.html +Request http://my_proxy.name/test.html => http://192.168.2.100:8081/test.html +~~~ + +~~~cpp +Configure UpstreamManager::upstream_add_server("my_proxy.name", "192.168.10.10"); +Request http://my_proxy.name:456/test.html => http://192.168.10.10:456/test.html +Request http://my_proxy.name/test.html => http://192.168.10.10:80/test.html +~~~ diff --git a/docs/en/tutorial-08-matrix_multiply.md b/docs/en/tutorial-08-matrix_multiply.md index f1c62e7d4c..94bcf3936a 100644 --- a/docs/en/tutorial-08-matrix_multiply.md +++ b/docs/en/tutorial-08-matrix_multiply.md @@ -2,7 +2,7 @@ # Sample code -[tutorial-08-matrix\_multiply.cc](../tutorial/tutorial-08-matrix_multiply.cc) +[tutorial-08-matrix\_multiply.cc](/tutorial/tutorial-08-matrix_multiply.cc) # About matrix\_multiply @@ -81,7 +81,7 @@ As the input matrices may be illegal in matrix multiplication, so there is an er # Generating computing tasks After you define the types of input and output and the algorithm process, you can use WFThreadTaskFactory to generate a computing task. -In [WFTaskFactory.h](../src/factory/WFTaskFactory.h), the computing task factory is defined as follows: +In [WFTaskFactory.h](/src/factory/WFTaskFactory.h), the computing task factory is defined as follows: ~~~cpp template @@ -155,7 +155,7 @@ The callback simply prints out the input and the output. If the input data are i In our system, algorithms and protocols are highly symmetrical on a very abstract level. There are thread tasks with user-defined algorithms, obviously there are network tasks with user-defined protocols. -A user-defined algorithm requires the user to provide the algorithm procedure, and a user-defined protocol requires the user to provide the procedure of serialization and deserialization. You can see an introduction in [Simple client/server based on user-defined protocols](./tutorial-10-user_defined_protocol.md) +A user-defined algorithm requires the user to provide the algorithm procedure, and a user-defined protocol requires the user to provide the procedure of serialization and deserialization. You can see an introduction in [Simple client/server based on user-defined protocols](/tutorial-10-user_defined_protocol.md) For the user-defined algorithms and the user-defined protocols, both must be very pure . For example, an algorithm is just a conversion procedure from INPUT to OUPUT, and the algorithm does not know the existence of task, series, etc. The implementation of an HTTP protocol only cares about serialization and deserialization, and does not need to care about the task definition. Instead, the HTTP protocol is referred to in an http task. diff --git a/docs/en/tutorial-09-http_file_server.md b/docs/en/tutorial-09-http_file_server.md index dc7be2965f..ccb7607d2a 100644 --- a/docs/en/tutorial-09-http_file_server.md +++ b/docs/en/tutorial-09-http_file_server.md @@ -2,7 +2,7 @@ # Sample code -[tutorial-09-http\_file\_server.cc](../tutorial/tutorial-09-http_file_server.cc) +[tutorial-09-http\_file\_server.cc](/tutorial/tutorial-09-http_file_server.cc) # About http\_file\_server @@ -80,7 +80,7 @@ void process(WFHttpTask *server_task, const char *root) ~~~ Unlike http\_proxy that generates a new HTTP client task, here a pread task is generated by the factory. -[WFAlgoTaskFactory.h](../src/factory/WFTaskFactory.h) contains the definitions of relevant interfaces. +[WFAlgoTaskFactory.h](/src/factory/WFTaskFactory.h) contains the definitions of relevant interfaces. ~~~cpp struct FileIOArgs @@ -112,7 +112,7 @@ public: Both pread and pwrite return WFFileIOTask. We do not distinguish between sort and psort, and we do not distinguish between client and server task. They all follow the same principle. In addition to these two interfaces, preadv and pwritev return WFFileVIOTask; fsync and fdsync return WFFileSyncTask. You can see the details in the header file. Currently, our interface requires users to open and close fd by themselves. We are developing a set of file management interfaces. In the future, users only need to pass file names, which is more friendly to cross-platform applications. -The example uses the user\_data field of the task to save the global data of the service. For larger services, we recommend to use series context. You can see the [proxy examples](../tutorial/tutorial-05-http_proxy.cc) for details. +The example uses the user\_data field of the task to save the global data of the service. For larger services, we recommend to use series context. You can see the [proxy examples](/tutorial/tutorial-05-http_proxy.cc) for details. # Handling file reading results diff --git a/docs/en/tutorial-10-user_defined_protocol.md b/docs/en/tutorial-10-user_defined_protocol.md index 71366429af..39308211f4 100644 --- a/docs/en/tutorial-10-user_defined_protocol.md +++ b/docs/en/tutorial-10-user_defined_protocol.md @@ -1,25 +1,25 @@ # A simple user-defined protocol: client/server -# Sample code +# Sample codes -[message.h](../tutorial/tutorial-10-user_defined_protocol/message.h) -[message.cc](../tutorial/tutorial-10-user_defined_protocol/message.cc) -[server.cc](../tutorial/tutorial-10-user_defined_protocol/server.cc) -[client.cc](../tutorial/tutorial-10-user_defined_protocol/client.cc) +[message.h](/tutorial/tutorial-10-user_defined_protocol/message.h) +[message.cc](/tutorial/tutorial-10-user_defined_protocol/message.cc) +[server.cc](/tutorial/tutorial-10-user_defined_protocol/server.cc) +[client.cc](/tutorial/tutorial-10-user_defined_protocol/client.cc) # About user\_defined\_protocol -This example demostrates a simple communication protocol, and builds a server and a client on that protocol. The server converts the message sent by client into the uppercase text and returns it to the client. +This example designs a simple communication protocol, and builds a server and a client on that protocol. The server converts the message sent by client into uppercase and returns it to the client. # Protocol format -The protocol message contains one 4-byte head and one message body. The head is an integer in network byte order, indicating the length of body. -The formats of the request messages and the response messages are the same. +The protocol message contains one 4-byte head and one message body. Head is an integer in network byte order, indicating the length of body. +The formats of the request messages and the response messages are identical. # Protocol implementation -A user-defined protocol should implement the serialization and deserialization interfaces, which are virtual functions in ProtocolMeessage class. -In addition, for the convenience of use, we strongly recommend users to implement the move structure and move assignment for messages (for **std::move()**). [ProtocolMessage.h](../src/protocol/ProtocolMessage.h) contains the following serialization and deserialization interfaces: +A user-defined protocol should provide its own serialization and deserialization methods, which are virtual functions in ProtocolMeessage class. +In addition, for the convenience of use, we strongly recommend users to implement the **move constructor** and **move assignment** for messages (for std::move ()). [ProtocolMessage.h](/src/protocol/ProtocolMessage.h) contains the following serialization and deserialization interfaces: ~~~cpp namespace protocol @@ -43,34 +43,33 @@ private: ### Serialization function: encode -* The **encode** function is called before the message is sent, and it is called only once for each message. -* In the **encode** function, you need to serialize the message into a vector array, and the number of elements in the array must not exceed **max**. Currently the value of **max** is 8192. -* For the definition of **struct iovec**, please see the system calls **readv** and **writev**. -* Normally the return value of the **encode** function is between 0 and **max**. It indicates the number of vector in the message. - * In case of UDP protocol, please note that the total length must not be more than 64k, and no more than 1024 vectors are used (in Linux, **writev** writes only 1024 vectors at one time). - * UDP protocol can only be used for a client, and a UDP server are not supported. -* The **encode** -1 indicates errors. To return -1, you need to set **errno**. If the return value > max, you will get an EOVERFLOW error. All errors are obtained in the callback. -* For performance reasons, the content pointed to by the **iov\_base** pointer in the vector will not be copied. So it generally points to the member of the message class. +* The encode function is called before the message is sent, and it is called only once for each message. +* In the encode function, you need to serialize the message into a vector array, and the number of array elements must not exceed max. Current the value of max is 8192. +* For the definition of **struct iovec**, please see the system calls **readv** or **writev**. +* Normally the return value of the encode function is between 0 and max, indicating how many vector are used in the message. + * In case of UDP protocol, please note that the total length must not be more than 64k, and no more than 1024 vectors are used (in Linux, writev writes only 1024 vectors at one time). + * UDP protocol can only be used for a client, and UDP server cannot be realized. +* The encode -1 indicates errors. To return -1, you need to set errno. If the return value is > max, you will get an EOVERFLOW error. All errors are obtained in the callback. +* For performance reasons, the content pointed to by the iov\_base pointer in the vector will not be copied. So it generally points to the member of the message class. ### Deserialization function: append -* The **append** function is called every time a data block is received. Therefore, for each message, it may be called multiple times. -* **buf** and **size** are the content and the length of received data block respectively. You need to move the data content. - * If you implement **append(const void \*buf, size\_t \*size)**, you can tell the framework the length of the data that is consumed each time by modifying **\* size**. remaining size = received size - consumed size. The remaining part of the **buf** will be received again when the append is called next time. This function is more convenient for protocol parsing. Of course, you can also move the whole content and manage it by yourself. In this case, you do not need to modify **\*size**. - * If UDP protocol is used, you must append a complete data packet in each appending. -* If the **append** function returns 0, it indicates that the message is incomplete and the transmission continues. The return value of 1 indicates the end of the message. -1 indicates errors, and you need to set **errno**. +* The append function is called every time a data block is received. Therefore, for each message, it may be called multiple times. +* buf and size are the content and the length of received data block respectively. You need to move the data content. + * If the interface **append(const void \*buf, size\_t \*size)** is implemented, you can tell the framework how much length is consumed at this time by modifying \* size. remaining size = received size - consumed size, and the remaining part of the buf will be received again when the append is called next time. This function is more convenient for protocol parsing. Of course, you can also move the whole content and manage it by yourself. In this case, you do not need to modify \*size. +* If the **append** function returns 0, it indicates that the message is incomplete and the transmission continues. The return value of 1 indicates the end of the message. -1 indicates errors, and you need to set errno. * In a word, the append function is used to tell the framework whether the message transmission is completed or not. Please don't perform complicated and unnecessary protocol parsing in the append. ### Setting the errno -* If **encode** or **append** returns -1 or other negative numbers, it should be interpreted as failure, and you should set the **errno** to pass the error reason. You can obtain this error in the callback. -* If the system calls or the library functions such as **libc** fail (for example, malloc), **libc** will definitely set **errno**, and you do not need to set it again. -* Some errors, such as illegal messages, are quite common. For example, EBADMSG or EMSGSIZE can be used to indicate that the message content is wrong or the message is too large respectively. -* You can use a value that exceeds the **errno** range defined in the system to indicate a user-defined error. Generally, you can use a value greater than 256. -* Please do not use a negative **errno**. Because negative numbers are used inside the framework to indicate SSL errors. +* If encode or append returns -1 or other negative numbers, it should be interpreted as failure, and you should set the errno to pass the error reason. You can obtain this error in the callback. +* If the system calls or the library functions such as libc fail (for example, malloc), libc will definitely set errno, and you do not need to set it again. +* Some errors of illegal messages are quite common. For example, EBADMSG or EMSGSIZE can be used to indicate that the message content is wrong and the message is too large respectively. +* You can use a value that exceeds the errno range defined in the system to indicate a user-defined error. Generally, you can use a value greater than 256. +* Please do not use a negative errno. Because negative numbers are used inside the framework to indicate SSL errors. In our example, the serialization and deserialization of messages are very simple. -The header file [message.h](../tutorial/tutorial-10-user_defined_protocol/message.h) contains the declarations of the request class and the response class. +The header file [message.h](/tutorial/tutorial-10-user_defined_protocol/message.h) declares the request class and the response class. ~~~cpp namespace protocol @@ -91,8 +90,8 @@ using TutorialResponse = TutorialMessage; ~~~ Both the request class and the response class belong to the same type of messages. You can directly introduce them with using. -Note that both the request and the response can be constructed with no arguments. In other words, you must provide a constructor with no arguments or no user-defined constructors. -[message.cc](../tutorial/tutorial-10-user_defined_protocol/message.cc) contains the implementation of **encode** and **append**: +Note that both the request and the response can be constructed without parameters. In other words, you must provide a constructor without parameters or no constructor. In addition, the response object may be destroyed and reconstruct during communication if retrial occurs, therefore it should be a RAII class, otherwise things will be complicated). +[message.cc](/tutorial/tutorial-10-user_defined_protocol/message.cc) contains the implementation of encode and append: ~~~cpp namespace protocol @@ -164,10 +163,10 @@ int TutorialMessage::append(const void *buf, size_t size) } ~~~ -The implementation of **encode** is very simple, in which two vectors are always pointing to the head and the body respectively. Note that the **iov\_base** pointer must point to a member of the message class. -When you use **append**, you should ensure that the 4-byte head is received completely before reading the message body. Moreover, we can't guarantee that the first **append** must contain a complete head, so the process is a little cumbersome. -The **append** implements the **size\_limit** function, and an EMSGSIZE error will be returned if the **size\_limit** is exceeded. You can ignore the **size_limit** field if you don't need to limit the message size. -Because we require the communication protocol is two way with a request and a response, users do not need to consider the so-called "TCP packet sticking" problem. The problem should be treated as an error message directly.   +The implementation of encode is very simple, in which two vectors are always, pointing to the head and the body respectively. Note that the iov\_base pointer must point to a member of the message class. +When you use append, you should ensure that the 4-byte head is received completely before reading the message body. Moreover, we can't guarantee that the first append must contain a complete head, so the process is a little cumbersome. +The append implements the size\_limit function, and an EMSGSIZE error will be returned if the size\_limit is exceeded. You can ignore the size_limit field if you don't need to limit the message size. +Because we require the communication protocol is two way with a request and a response, users do not need to consider the so-called "TCP packet sticking" problem. The problem should be treated as an error message directly. Now, with the definition and implementation of messages, we can build a server and a client. # Server and client definitions @@ -184,7 +183,7 @@ using WFHttpServer = WFServer; ~~~ -Similarly, for the protocol in this tutorial, there is no difference in the definitions of the data types: +Similarly, for the protocol in this tutorial, there is no difference in the definitions of data types: ~~~cpp using WFTutorialTask = WFNetworkTask; ~~~ -# Server +# server There is no difference between this server and an ordinary HTTP server. We give priority to IPv6 startup, which does not affect the client requests in IPv4. In addition, the maximum request size is limited to 4KB. -Please see [server.cc](../tutorial/tutorial-10-user_defined_protocol/server.cc) for the complete code. +Please see [server.cc](/tutorial/tutorial-10-user_defined_protocol/server.cc) for the complete code. -# Client +# client -The client is used to receive the user input from the standard IO, construct a request, send it to the server and get the results. +The logic of the client is to receive the user input from standard IO, construct a request, send it to the server and get the results. For simplicity, the process of reading standard input is completed in the callback, so we will send an empty request first. Also, for the sake of security, we limit the packet size of the server reply to 4KB. -The only thing that a client needs to know is how to generate a client task on a user-defined protocol. You can use one of the three interfaces in [WFTaskFactory.h](../src/factory/WFTaskFactory.h): +The only thing that a client needs to know is how to generate a client task on a user-defined protocol. There are three interface options in [WFTaskFactory.h](/src/factory/WFTaskFactory.h): ~~~cpp template @@ -234,8 +233,8 @@ public: }; ~~~ -Among them, TransportType specifies the transport layer protocol, and the current options include TT\_TCP, TT\_UDP, TT\_SCTP and TT\_TCP\_SSL. -There is little difference between the three interfaces. In the example, the URL is not needed for the time being. A domain name and a port is used to create a task. +Among them, TransportType specifies the transport layer protocol, and the current options include TT\_TCP, TT\_UDP, TT\_SCTP, TT\_TCP\_SSL and TT\_SCTP\_SSL. +There is little difference between the three interfaces. In our example, the URL is not needed for the time being. We use a domain name and a port to create a task. The actual code is shown as follows. We inherited the WFTaskFactory class, but this derivation is not required. ~~~cpp @@ -259,9 +258,9 @@ public: }; ~~~ -You can see that **WFNetworkTaskFactory\** class is used to create a client task. +You can see that we used the WFNetworkTaskFactory\ class to create a client task. Next, by calling the **set\_keep\_alive()** interface of the task, the connection is kept for 30 seconds after the communication is completed. Otherwise, the short connection will be used by default. -The previous examples have explained the knowledge required for understanding other codes of the above client. Please see [client.cc](../tutorial/tutorial-10-user_defined_protocol/client.cc). +The previous examples have explained the knowledge in other codes of the above client. Please see [client.cc](/tutorial/tutorial-10-user_defined_protocol/client.cc). # How is the request on an built-in protocol generated @@ -271,7 +270,7 @@ Currently, there are four built-in protocols in the framework: HTTP, Redis, MySQ WFHttpTask *task = WFNetworkTaskFactory::create_client_task(...); ~~~ -Please note that an HTTP task generated in this way will lose a lot of functions. For example, it is impossible to identify whether to use persistent connection according to the header, and it is impossible to identify redirection, and etc. +Please note that an HTTP task generated in this way will lose a lot of functions. For example, it is impossible to identify whether to use persistent connection according to the header, and it is impossible to identify redirection, etc. Similarly, if a MySQL task is generated in this way, it may not run at all, because there is no login authentication process. -A Kafka request may need to have complicated interactions with multiple brokers, so a request created in this way obviously cannot complete this process. -This shows that the generation of one message in each built-in protocol is far more complicated than that in this example. Similarly, if you need to implement a communication protocol with more functions, you need to add many code lines. \ No newline at end of file +A Kafka request may need to have complicated interactions with multiple brokers, so the request created in this way obviously cannot complete this process. +This shows that the generation of one message in each built-in protocol is far more complicated than that in this example. Similarly, if you need to implement a communication protocol with more functions, there are still many codes to write. diff --git a/docs/en/tutorial-12-mysql_cli.md b/docs/en/tutorial-12-mysql_cli.md index a33de7061a..896cd31d0c 100644 --- a/docs/en/tutorial-12-mysql_cli.md +++ b/docs/en/tutorial-12-mysql_cli.md @@ -6,25 +6,27 @@ # About mysql\_cli -The mysql\_cli in the tutorial is used in a way similar to the official client. It is an asynchronous MySQL client with an interactive command line interface. +The usage of mysql\_cli in the tutorial is similar to that of the official client. It is an asynchronous MySQL client with an interactive command line interface. -Run the program with the command: ./mysql_cli \ +To start the program, run the command: ./mysql_cli \ -After startup, you can directly enter MySQL command in the terminal to interact with db, or enter quit or Ctrl-C to exit. +After startup, you can directly enter MySQL command in the terminal to interact with db, or enter `quit` or `Ctrl-C` to exit. # Format of MySQL URL -mysql://username:password@host:port/dbname?character\_set=charset +mysql://username:password@host:port/dbname?character\_set=charset&character\_set\_results=charset -- fill in username and password according to the requirements; +- fill in the username and the password for the MySQL database; - the default port number is 3306; -- dbname is the name of the database to be used. It is recommended to fill in the db name if SQL statements only operates on one db; +- **dbname** is the name of the database to be used. It is recommended to provide a dbname if SQL statements only operates on one database; -- If you have upstream selection requirements at this level, please see [upstream documents](../docs/about-upstream.md). +- If you have upstream selection requirements for MySQL, please see [upstream documents](/docs/en/about-upstream.md). -- charset indicates a character set; the default value is utf8. For details, please see official MySQL documents [character-set.html](https://dev.mysql.com/doc/internals/en/character-set.html). +- **character_set** indicates a character set used for the client, with the same meaning of --default-character-set in official client. The default value is utf8. For details, please see official MySQL documents [character-set.html](https://dev.mysql.com/doc/internals/en/character-set.html). + +- **character_set_results** indicates a character set for client, connection and results. If you wants to use `SET NAMES ` in SQL statements, please set it here. Sample MySQL URL: @@ -34,7 +36,7 @@ mysql://@test.mysql.com:3306/db1?character\_set=utf8 # Creating and starting a MySQL task -You can use WFTaskFactory to create a MySQL task. The creation interface and callback functions are used in a way similar to other tasks in the workflow: +You can use WFTaskFactory to create a MySQL task. The usage of creating interface and callback functions are similar to other tasks in workflow: ~~~cpp using mysql_callback_t = std::function; @@ -44,13 +46,13 @@ WFMySQLTask *create_mysql_task(const std::string& url, int retry_max, mysql_call void set_query(const std::string& query); ~~~ -You can call **set\_query()** on req to write SQL statements after creating a WFMySQLTask. +You can call **set\_query()** on the request to write SQL statements after creating a WFMySQLTask. -According to the MySQL protocol, if an empty packet is sent after the connection is established, the server will wait instead of returning a packet, so the user will get a timeout. Therefore, the framework makes special check on those task that do not call `set_query()` and will immediately return **WFT\_ERR\_MYSQL\_QUERY\_NOT\_SET**. +According to the MySQL protocol, if an empty packet is sent after the connection is established, the server will wait instead of returning a packet, so the user will get a timeout. Therefore, the framework makes special check on those tasks that do not call `set_query()` and will immediately return **WFT\_ERR\_MYSQL\_QUERY\_NOT\_SET**. -Others functions, including callback, series and user\_data are used in a way similar to other tasks in the workflow. +Other functions, including callback, series and user\_data are used in a way similar to other tasks in workflow. -The following code shows some general usage: +The following codes show some general usage: ~~~cpp int main(int argc, char *argv[]) { @@ -65,51 +67,55 @@ int main(int argc, char *argv[]) # Supported commands -Currently the supported command is **COM\_QUERY**, which can cover the basic requirements for adding, deleting, modifying and querying data, creating and deleting databases, creating and deleting tables, preparing, using stored procedures and using transactions. +Currently the supported command is **COM\_QUERY**, which can cover the basic requirements for adding, deleting, modifying and querying data, creating and deleting databases, creating and deleting tables, prepare, using stored procedures and using transactions. -Because the program don't support the selection of databases (**US**E command) in our interactive commands, if there are **cross-database** operations in SQL statements, you can specify the database and table with **db \_ name.table \_ name**. +Because the program doesn't support the selection of databases (**USE** command) in our interactive commands, if there are **cross-database** operations in SQL statements, you can specify the database and table with **db\_name.table\_name**. -**Multiple commands** can be spliced together and then passed to WFMySQLTask with `set_query()`. Generally speaking, multiple statements can get all the results back at one time. However, due to the incompatibility between the packet reply method in the MySQL protocol and our question-and-answer communication in some special cases, please read the following cautions before you add the SQL statements in `set_query()`: +**Multiple commands** can be joined together and then passed to WFMySQLTask with `set_query()`. Generally speaking, multiple statements can get all the results back at one time. However, as the packet return method in the MySQL protocol is not compatible with question and answer communication under some provisions, please read the following cautions before you add the SQL statements in `set_query()`: - For the statement that gets a single result set, multiple statements can be spliced (ordinary INSERT/UPDATE/SELECT/PREPARE) -- The statement that returns multiple result sets (such as CALL a stored procedure) is also supported. +- One statement that returns multiple result sets (such as CALL a stored procedure) is also supported. - In other cases, it is recommended to divide SQL statements into individual requests For example: ~~~cpp -// 单结果集的多条语句拼接,可以正确拿到返回结果 +// Correct! +// This will get multiple result sets for mutiple ordinary statements. req->set_query("SELECT * FROM table1; SELECT * FROM table2; INSERT INTO table3 (id) VALUES (1);"); -// 多结果集的单条语句,也可以正确拿到返回结果 +// Correct! +// This will get single or multiple result sets for some multi-result-set statements. req->set_query("CALL procedure1();"); -// 多结果集与其他拼接都不能完全拿到所有返回结果 +// Incorrect! +// This contains a multi-result-set statement and other statements. +// Cannot get all the result sets correctly. req->set_query("CALL procedure1(); SELECT * FROM table1;"); ~~~ # Parsing results -Similar to other tasks in the workflow, you can use **task->get\_resp()** to get **MySQLResponse**, and you can use **MySQLResultCursor** to traverse the result set, the infomation of each column of the result set(**MySQLField**), each row and each **MySQLCell**. For details on the interfaces, please see [MySQLResult.h](../src/protocol/MySQLResult.h). +Similar to other tasks in workflow, you can use **task->get\_resp()** to get **MySQLResponse**, and you can use **MySQLResultCursor** to traverse the result set, the infomation of each column of the result set (**MySQLField**), each row and each **MySQLCell**. For details on the interfaces, please see [MySQLResult.h](/src/protocol/MySQLResult.h). -The specific steps from the external to the internal should be: +The specific steps should be: -1. checking the task state (state at communication level): you can check whether the task is successfully executed by checking whether **task->get\_state()** is equal to WFT\_STATE\_SUCCESS; +1. checking the task state (state at communication): you can check whether the task is successfully executed by checking whether **task->get\_state()** is equal to WFT\_STATE\_SUCCESS; -2. determining the type of the reply packet (the parsing status of the return packet): call **resp->get\_packet\_type()** to check the type of the MySQL return packet. The common types include: +2. determining the type of the reply packet (state at parsing the return packet): call **resp->get\_packet\_type()** to check the type of the MySQL return packet. The common types include: -- MYSQL\_PACKET\_OK: non-result set requests: parsed successfully; -- MYSQL\_PACKET\_EOF: result set requests: parsed successfully; +- MYSQL\_PACKET\_OK: non-result-set requests: parsed successfully; +- MYSQL\_PACKET\_EOF: result-set requests: parsed successfully; - MYSQL\_PACKET\_ERROR: requests: failed; -3. checking the result set state (the state for reading the result sets): you can use MySQLResultCursor to read the content in the result set. Because the data returned by a MySQL server contains multiple result sets, the cursor will **automatically point to the reading position of the first result set** at first. **cursor->get\_cursor\_status()** returns the following states: +3. checking the result set state (state at reading the result sets): you can use MySQLResultCursor to read the content in the result set. Because the data returned by a MySQL server contains multiple result sets, the cursor will **automatically point to the reading position of the first result set** at first. **cursor->get\_cursor\_status()** returns the following states: - MYSQL\_STATUS\_GET\_RESULT: the data are available to read; - MYSQL\_STATUS\_END: the last record of the current result set has been read; -- MYSQL\_STATUS\_EOF: all result sets are fetched; -- MYSQL\_STATUS\_OK: the reply packet is a non-result set packet, so you do not need to read data through the result set interface; +- MYSQL\_STATUS\_EOF: all result-sets are fetched; +- MYSQL\_STATUS\_OK: the reply packet is a non-result-set packet, so you do not need to read data through the result set interface; - MYSQL\_STATUS\_ERROR: parsing error; 4. reading each field of the columns: @@ -132,7 +138,7 @@ The specific steps from the external to the internal should be: 7. returning to the head of the current result set: if it is necessary to read this result set again, you can use **cursor->rewind()** to return to the head of the current result set, and then read it via the operations in Step 5 or Step 6; -8. getting the next result set: because the data packet returned by MySQL server may contain multiple result sets (for example, each select statement gets a result set; or the multiple result sets returned by calling a procedure). Therefore, you can use **cursor->next\_result\_set()** to jump to the next result set. If the return value is false, it means that all result sets have been taken. +8. getting the next result set: because the data packet returned by MySQL server may contains multiple result sets (for example, each select statement gets a result set; or the multiple result sets returned by calling a procedure). Therefore, you can use **cursor->next\_result\_set()** to jump to the next result set. If the return value is false, it means that all result sets have been taken. 9. returning to the first result set: use **cursor->first\_result\_set()** to return to the heads of all result sets, and then you can repeat the operations from Step 3. @@ -148,7 +154,7 @@ The whole example is shown below: ~~~cpp void task_callback(WFMySQLTask *task) { - // step-1. 判断任务状态 + // step-1. Check the status of the task if (task->get_state() != WFT_STATE_SUCCESS) { fprintf(stderr, "task error = %d\n", task->get_error()); @@ -160,7 +166,7 @@ void task_callback(WFMySQLTask *task) bool test_rewind_flag = false; begin: - // step-2. 判断回复包状态 + // step-2. Check the status of reply packet switch (resp->get_packet_type()) { case MYSQL_PACKET_OK: @@ -174,11 +180,11 @@ begin: do { fprintf(stderr, "cursor_status=%d field_count=%u rows_count=%u ", cursor.get_cursor_status(), cursor.get_field_count(), cursor.get_rows_count()); - // step-3. 判断结果集状态 + // step-3. Check the status of the result set if (cursor.get_cursor_status() != MYSQL_STATUS_GET_RESULT) break; - // step-4. 读取每个fields。这是个nocopy api + // step-4. Read each fields. This is a nocopy api const MySQLField *const *fields = cursor.fetch_fields(); for (int i = 0; i < cursor.get_field_count(); i++) { @@ -187,18 +193,18 @@ begin: fields[i]->get_name().c_str(), datatype2str(fields[i]->get_data_type())); } - // step-6. 把所有行读出,也可以while (cursor.fetch_row(map/vector)) 按step-5拿每一行 + // step-6. Read all the rows. You may use while (cursor.fetch_row(map/vector)) to get each rows accoding to step-5. std::vector> rows; cursor.fetch_all(rows); for (unsigned int j = 0; j < rows.size(); j++) { - // step-10. 具体每个cell的读取 + // step-10. Read each cell for (unsigned int i = 0; i < rows[j].size(); i++) { fprintf(stderr, "[%s][%s]", fields[i]->get_name().c_str(), datatype2str(rows[j][i].get_data_type())); - // step-10. 判断具体类型is_string()和转换具体类型as_string() + // step-10. Check the type wih is_string()and transform the type with as_string() if (rows[j][i].is_string()) { std::string res = rows[j][i].as_string(); @@ -208,13 +214,13 @@ begin: } // else if ... } } - // step-8. 拿下一个结果集 + // step-8. Get the next result set } while (cursor.next_result_set()); if (test_first_result_set_flag == false) { test_first_result_set_flag = true; - // step-9. 返回第一个结果集 + // step-9. Go back to the first result set cursor.first_result_set(); goto begin; } @@ -222,7 +228,7 @@ begin: if (test_rewind_flag == false) { test_rewind_flag = true; - // step-7. 返回当前结果集头部 + // step-7. Go back to the first of the current result set cursor.rewind(); goto begin; } @@ -238,11 +244,11 @@ begin: # WFMySQLConnection -Since it is a highly concurrent asynchronous client, this means that the client may have more than one connection to a server. As both MySQL transactions and preparation are stateful, in order to ensure that one transaction or preparation ocupies one connection exclusively, you can use our encapsulated secondary factory WFMySQLConnection to create a task. Each WFMySQLConnection guarantees that one connection is occupied exclusively. For the details, please see [WFMySQLConnection.h](../src/client/WFMySQLConnection.h). +Since it is a highly concurrent asynchronous client, this means that the client may have more than one connection to the server. As both MySQL transactions and preparation are stateful, in order to ensure that one transaction or preparation ocupies one connection exclusively, you can use our encapsulated secondary factory WFMySQLConnection to create a task. Each WFMySQLConnection guarantees that one connection is occupied exclusively. For the details, please see [WFMySQLConnection.h](/src/client/WFMySQLConnection.h). ### 1\. Creating and initializing WFMySQLConnection -When creating a WFMySQLConnection, you need to pass in globally unique **id**, and the framework will use the id to find the corresponding unique connection in subsequent calls. +When creating a WFMySQLConnection, you need to pass in globally unique **id**, and the subsequent calls on this WFMySQLConnection will use this id to find the corresponding unique connection. When initializing a WFMySQLConnection, you need to pass a URL, and then you do not need to set the URL for the task created on this connection. @@ -258,11 +264,11 @@ public: ### 2\. Creating a task and closing a connection -With **create\_query\_task()**, you can create a task by writing an SQL request and a callback function. The task must be sent on this connection. +With **create\_query\_task()**, you can create a task by writing an SQL request and a callback function. The task is garuanteed to be sent on this connection. -Sometimes you need to close this connection manually. Because when you stop using it, this connection will be kept until MySQL server times out. During this period, if you use the same id and url to create a WFMySQLConnection, you reuse the connection. +Sometimes you need to close this connection manually. Because when you stop using it, this connection will be kept until MySQL server time out. During this period, if you use the same id and url to create a WFMySQLConnection, you may reuse the connection. -Therefore, we suggest that if you are not ready to reuse the connection, you should use **create\_disconnect\_task()** to create a task and manually close the connection. +Therefore, we suggest that if you do not want to reuse the connection, you should use **create\_disconnect\_task()** to create a task and manually close the connection. ~~~cpp class WFMySQLConnection @@ -288,7 +294,7 @@ WFMySQLConnection is equivalent to a secondary factory. In the framework, we arr ### 3\. Cautions -If you have started BEGIN but have not COMMIT or ROLLBACK during the transaction and the connection has been interrupted during the transaction, the connection will be automatically reconnected internally by the framework, and you will get **ECONNRESET** error in the next task request. In this case, the transaction statement that is not COMMIT has expired and you need to sent it again. +If you have started `BEGIN` but have not `COMMIT` or `ROLLBACK` during the transaction and the connection has been interrupted during the transaction, the connection will be automatically reconnected internally by the framework, and you will get **ECONNRESET** error in the next task request. In this case, the transaction statements those have not been `COMMIT` would be expired and you may need to send them again. ### 4\. Preparation @@ -311,4 +317,4 @@ query = "COMMIT;"; WFMySQLTask *t4 = conn.create_query_task(query, task_callback); WFMySQLTask *t5 = conn.create_disconnect_task(task_callback); ((*t1) > t2 > t3 > t4 > t5).start(); -~~~ \ No newline at end of file +~~~