Repository: lf-edge/ekuiper
Pull request #1676 (2023-03-16 09:27:02)

fix(*): port recent fixes into 1.8.x


Patch:
diff --git a/docs/en_US/configuration/global_configurations.md b/docs/en_US/configuration/global_configurations.md
index d83fe008..ef72ba0b 100644
--- a/docs/en_US/configuration/global_configurations.md
+++ b/docs/en_US/configuration/global_configurations.md
@@ -191,6 +191,8 @@ This section configures the portable plugin runtime.
       # The executable of python. Specify this if you have multiple python instances in your system
       # or other circumstance where the python executable cannot be successfully invoked through the default command.
       pythonBin: python
+      # control init timeout in ms. If the init time is longer than this value, the plugin will be terminated.
+      initTimeout: 5000
 ```
 
 ## Ruleset Provision
diff --git a/docs/zh_CN/configuration/global_configurations.md b/docs/zh_CN/configuration/global_configurations.md
index dd26f984..9e9fac3f 100644
--- a/docs/zh_CN/configuration/global_configurations.md
+++ b/docs/zh_CN/configuration/global_configurations.md
@@ -191,6 +191,8 @@ GET http://localhost:9081/plugins/functions/prebuild
       # 配置 python 可执行文件的位置或命令。
       # 若系统中有多个 python 版本，可通过此配置指定具体的 python 地址。
       pythonBin: python
+      # 控制插件初始化超时时间，单位为毫秒。eKuiper portable 插件运行时会等待插件初始化以完成握手，若超时则终止插件进程
+      initTimeout: 5000
 ```
 
 ## 初始化规则集
diff --git a/etc/functions/internal.json b/etc/functions/internal.json
index 4d2042d8..aee42904 100644
--- a/etc/functions/internal.json
+++ b/etc/functions/internal.json
@@ -246,7 +246,7 @@
 			"category": "function",
 			"icon": "iconPath",
 			"label": {
-				"en_US": "population standard deviation",
+				"en_US": "Population Standard Deviation",
 				"zh_CN": "总体标准偏差"
 			}
 		}
@@ -285,7 +285,7 @@
 			"category": "function",
 			"icon": "iconPath",
 			"label": {
-				"en_US": "sample standard deviation",
+				"en_US": "Sample Standard Deviation",
 				"zh_CN": "样本标准偏差"
 			}
 		}
@@ -324,8 +324,8 @@
 			"category": "function",
 			"icon": "iconPath",
 			"label": {
-				"en_US": "population variance",
-				"zh_CN": "总体标准偏差的方差"
+				"en_US": "Population Variance",
+				"zh_CN": "总体方差"
 			}
 		}
 	},{
@@ -363,8 +363,8 @@
 			"category": "function",
 			"icon": "iconPath",
 			"label": {
-				"en_US": "sample variance",
-				"zh_CN": "样本标准偏差的方差"
+				"en_US": "Sample Variance",
+				"zh_CN": "样本方差"
 			}
 		}
 	}, {
@@ -391,7 +391,7 @@
 				}
 			},
 			{
-				"name": "field",
+				"name": "percentile",
 				"optional": false,
 				"control": "field",
 				"type": "number",
@@ -400,7 +400,7 @@
 					"zh_CN": "要查找的值的百分位数。百分位数必须是介于 0.0 和 1.0 之间的常数。"
 				},
 				"label": {
-					"en_US": "percentile",
+					"en_US": "Percentile",
 					"zh_CN": "百分位数"
 				}
 			}
@@ -408,7 +408,7 @@
 		"return": {
 			"type": "number",
 			"hint": {
-				"en_US": "percentile value based on a continuous distribution",
+				"en_US": "Percentile value based on a continuous distribution",
 				"zh_CN": "连续分布的百分位值"
 			}
 		},
@@ -416,7 +416,7 @@
 			"category": "function",
 			"icon": "iconPath",
 			"label": {
-				"en_US": "percentile value based on a continuous distribution",
+				"en_US": "Percentile Values of Continuous Distribution",
 				"zh_CN": "连续分布的百分位值"
 			}
 		}
@@ -444,7 +444,7 @@
 				}
 			},
 			{
-				"name": "field",
+				"name": "percentile",
 				"optional": false,
 				"control": "field",
 				"type": "number",
@@ -453,7 +453,7 @@
 					"zh_CN": "要查找的值的百分位数。百分位数必须是介于 0.0 和 1.0 之间的常数。"
 				},
 				"label": {
-					"en_US": "percentile",
+					"en_US": "Percentile",
 					"zh_CN": "百分位数"
 				}
 			}
@@ -461,7 +461,7 @@
 		"return": {
 			"type": "number",
 			"hint": {
-				"en_US": "percentile value based on a discrete distribution",
+				"en_US": "Percentile value based on a discrete distribution",
 				"zh_CN": "离散分布的百分位值"
 			}
 		},
@@ -469,7 +469,7 @@
 			"category": "function",
 			"icon": "iconPath",
 			"label": {
-				"en_US": "percentile value based on a discrete distribution",
+				"en_US": "Percentile Value of Discrete Distribution",
 				"zh_CN": "离散分布的百分位值"
 			}
 		}
@@ -561,7 +561,7 @@
 			"category": "function",
 			"icon": "iconPath",
 			"label": {
-				"en_US": "deduplicate",
+				"en_US": "Deduplicate",
 				"zh_CN": "去重"
 			}
 		}
@@ -2869,7 +2869,7 @@
 			"category": "function",
 			"icon": "iconPath",
 			"label": {
-				"en_US": "SHA 512 hashed value of the argument",
+				"en_US": "SHA 512",
 				"zh_CN": "参数的 SHA512 哈希值"
 			}
 		}
@@ -3113,44 +3113,6 @@
 				"zh_CN": "时间戳"
 			}
 		}
-	}, {
-		"name": "mqtt",
-		"example": "mqtt(topic)",
-		"hint": {
-			"en_US": "Returns the MQTT meta-data of specified key. The current supported keys - topic: return the topic of message. If there are multiple stream source, then specify the source name in parameter. Such as mqtt(src1.topic) - messageid: return the message id of message. If there are multiple stream source, then specify the source name in parameter. Such as mqtt(src2.messageid)",
-			"zh_CN": "返回指定键的 MQTT 元数据。 当前支持的键包括 -topic：返回消息的主题。 如果有多个流源，则在参数中指定源名称。 如 mqtt(src1.topic) - messageid：返回消息的消息ID。 如果有多个流源，则在参数中指定源名称。 如 mqtt(src2.messageid)"
-		},
-		"args": [
-			{
-				"name": "key",
-				"optional": false,
-				"control": "text",
-				"type": "any",
-				"hint": {
-					"en_US": "Returns the MQTT meta-data of specified key.",
-					"zh_CN": "返回指定键的 MQTT 元数据"
-				},
-				"label": {
-					"en_US": "Returns the MQTT meta-data of specified key.",
-					"zh_CN": "返回指定键的 MQTT 元数据"
-				}
-			}
-		],
-		"return": {
-			"type": "any",
-			"hint": {
-				"en_US": "MQTT Meta",
-				"zh_CN": "MQTT 元数据"
-			}
-		},
-		"node": {
-			"category": "function",
-			"icon": "iconPath",
-			"label": {
-				"en_US": "MQTT Meta",
-				"zh_CN": "MQTT 元数据"
-			}
-		}
 	}, {
 		"name": "meta",
 		"example": "meta(topic)",
diff --git a/etc/kuiper.yaml b/etc/kuiper.yaml
index 12eebe0a..a1bbab73 100644
--- a/etc/kuiper.yaml
+++ b/etc/kuiper.yaml
@@ -99,4 +99,6 @@ store:
 portable:
   # The executable of python. Specify this if you have multiple python instances in your system
   # or other circumstance where the python executable cannot be successfully invoked through the default command.
-  pythonBin: python
\ No newline at end of file
+  pythonBin: python
+  # control init timeout in ms. If the init time is longer than this value, the plugin will be terminated.
+  initTimeout: 5000
\ No newline at end of file
diff --git a/etc/ops/function.json b/etc/ops/function.json
index a692397f..cd10715a 100644
--- a/etc/ops/function.json
+++ b/etc/ops/function.json
@@ -13,8 +13,8 @@
       "zh_CN": "https://github.com/lf-edge/ekuiper/blob/master/docs/zh_CN/sqls/query_language_elements.md"
     },
     "description": {
-      "en_US": "An operation to invoke a function",
-      "zh_CN": "用于调用函数的操作"
+      "en_US": "An operation to invoke a function or multiple functions",
+      "zh_CN": "用于调用或嵌套调用函数的操作"
     }
   },
   "properties": [{
diff --git a/etc/ops/script.json b/etc/ops/script.json
index 081024b1..43a56d15 100644
--- a/etc/ops/script.json
+++ b/etc/ops/script.json
@@ -21,7 +21,7 @@
     "name": "script",
     "default": "",
     "optional": false,
-    "control": "text",
+    "control": "textarea",
     "type": "string",
     "hint": {
       "en_US": "The script text, now supports JavaScript only. Must have a function named exec, whose parameter is the message and the return value must be the processed message.",
diff --git a/etc/sinks/edgex.json b/etc/sinks/edgex.json
index 03a618e0..9325bcb5 100644
--- a/etc/sinks/edgex.json
+++ b/etc/sinks/edgex.json
@@ -423,8 +423,8 @@
     "category": "sink",
     "icon": "iconPath",
     "label": {
-      "en": "EDGEX",
-      "zh": "EDGEX"
+      "en": "EdgeX Foundry",
+      "zh": "EdgeX Foundry"
     }
   }
 }
diff --git a/etc/sources/edgex.json b/etc/sources/edgex.json
index 0e492b72..bc0a98a1 100644
--- a/etc/sources/edgex.json
+++ b/etc/sources/edgex.json
@@ -355,8 +355,8 @@
 		"category": "source",
 		"icon": "iconPath",
 		"label": {
-			"en_US": "Edgex",
-			"zh_CN": "Edgex"
+			"en_US": "EdgeX Foundry",
+			"zh_CN": "EdgeX Foundry"
 		}
 	}
 }
diff --git a/extensions/functions/accumulateWordCount/accumulateWordCount.json b/extensions/functions/accumulateWordCount/accumulateWordCount.json
index 59c41bff..22ff0e1e 100644
--- a/extensions/functions/accumulateWordCount/accumulateWordCount.json
+++ b/extensions/functions/accumulateWordCount/accumulateWordCount.json
@@ -27,20 +27,18 @@
 		},
 		"args": [
 			{
-				"name": "inputData",
-				"hidden": true,
+				"name": "field",
 				"optional": false,
-				"control": "text",
+				"control": "field",
 				"type": "string",
 				"hint": {
-					"en_US": "Input data",
-					"zh_CN": "输入数据"
+					"en_US": "The field to calculate the word count.",
+					"zh_CN": "用于计算字数的字段名"
 				},
 				"label": {
-					"en_US": "Input data",
-					"zh_CN": "输入数据"
-				},
-				"values": []
+					"en_US": "Field",
+					"zh_CN": "字段"
+				}
 			}
 		],
 		"return": {
@@ -54,8 +52,8 @@
 			"category": "function",
 			"icon": "iconPath",
 			"label": {
-				"en_US": "AccumulateWordCount",
-				"zh_CN": "AccumulateWordCount"
+				"en_US": "Accumulate Word Count",
+				"zh_CN": "累计字数"
 			}
 		}
 	}]
diff --git a/extensions/functions/countPlusOne/countPlusOne.json b/extensions/functions/countPlusOne/countPlusOne.json
index bb3db119..6c053eb7 100644
--- a/extensions/functions/countPlusOne/countPlusOne.json
+++ b/extensions/functions/countPlusOne/countPlusOne.json
@@ -27,18 +27,17 @@
 		},
 		"args": [
 			{
-				"name": "inputData",
-				"hidden": true,
+				"name": "field",
 				"optional": false,
-				"control": "text",
-				"type": "int",
+				"control": "field",
+				"type": "any",
 				"hint": {
-					"en_US": "Input data",
-					"zh_CN": "输入数据"
+					"en_US": "The field to calculate the count.",
+					"zh_CN": "用于计数的字段名"
 				},
 				"label": {
-					"en_US": "Input data",
-					"zh_CN": "输入数据"
+					"en_US": "Field",
+					"zh_CN": "字段"
 				}
 			}
 		],
@@ -53,8 +52,8 @@
 			"category": "function",
 			"icon": "iconPath",
 			"label": {
-				"en_US": "countPlusOne",
-				"zh_CN": "countPlusOne"
+				"en_US": "Count Plus One",
+				"zh_CN": "计数后加一"
 			}
 		}
 	}]
diff --git a/extensions/functions/geohash/geohash.json b/extensions/functions/geohash/geohash.json
index 14581079..391d2c1c 100644
--- a/extensions/functions/geohash/geohash.json
+++ b/extensions/functions/geohash/geohash.json
@@ -27,32 +27,30 @@
 		},
 		"args": [
 			{
-				"name": "la",
-				"hidden": true,
+				"name": "latitude",
 				"optional": false,
-				"control": "text",
+				"control": "field",
 				"type": "string",
 				"hint": {
-					"en_US": "Input la",
+					"en_US": "Input latitude",
 					"zh_CN": "输入经度"
 				},
 				"label": {
-					"en_US": "Input la",
-					"zh_CN": "输入经度"
+					"en_US": "Latitude",
+					"zh_CN": "经度"
 				}
 			},
 			{
-				"name": "lo",
-				"hidden": true,
+				"name": "longitude",
 				"optional": false,
-				"control": "text",
+				"control": "field",
 				"type": "string",
 				"hint": {
-					"en_US": "Input lo",
+					"en_US": "Input longitude",
 					"zh_CN": "输入纬度"
 				},
 				"label": {
-					"en_US": "Input lo",
+					"en_US": "Longitude",
 					"zh_CN": "输入纬度"
 				}
 			}
@@ -68,8 +66,8 @@
 			"category": "function",
 			"icon": "iconPath",
 			"label": {
-				"en_US": "geohashEncode",
-				"zh_CN": "geohashEncode"
+				"en_US": "Geohash Encode",
+				"zh_CN": "Geohash 编码"
 			}
 		}
 	}, {
@@ -81,32 +79,30 @@
 		},
 		"args": [
 			{
-				"name": "la",
-				"hidden": true,
+				"name": "latitude",
 				"optional": false,
-				"control": "text",
+				"control": "field",
 				"type": "string",
 				"hint": {
-					"en_US": "Input la",
+					"en_US": "Input latitude",
 					"zh_CN": "输入经度"
 				},
 				"label": {
-					"en_US": "Input la",
-					"zh_CN": "输入经度"
+					"en_US": "Latitude",
+					"zh_CN": "经度"
 				}
 			},
 			{
-				"name": "lo",
-				"hidden": true,
+				"name": "longitude",
 				"optional": false,
-				"control": "text",
+				"control": "field",
 				"type": "string",
 				"hint": {
-					"en_US": "Input lo",
+					"en_US": "Input longitude",
 					"zh_CN": "输入纬度"
 				},
 				"label": {
-					"en_US": "Input lo",
+					"en_US": "Longitude",
 					"zh_CN": "输入纬度"
 				}
 			}
@@ -122,8 +118,8 @@
 			"category": "function",
 			"icon": "iconPath",
 			"label": {
-				"en_US": "geohashEncodeInt",
-				"zh_CN": "geohashEncodeInt"
+				"en_US": "Geohash Encode to Integer",
+				"zh_CN": "Geohash 整数编码"
 			}
 		}
 	}, {
@@ -136,16 +132,16 @@
 		"args": [
 			{
 				"name": "data",
-				"hidden": true,
+				"hidden": false,
 				"optional": false,
-				"control": "text",
+				"control": "field",
 				"type": "string",
 				"hint": {
 					"en_US": "Input data",
 					"zh_CN": "输入数据"
 				},
 				"label": {
-					"en_US": "Input data",
+					"en_US": "Field",
 					"zh_CN": "输入数据"
 				}
 			}
@@ -161,8 +157,8 @@
 			"category": "function",
 			"icon": "iconPath",
 			"label": {
-				"en_US": "geohashDecode",
-				"zh_CN": "geohashDecode"
+				"en_US": "Geohash Decode",
+				"zh_CN": "Geohash 解码"
 			}
 		}
 	}, {
@@ -175,16 +171,16 @@
 		"args": [
 			{
 				"name": "data",
-				"hidden": true,
+				"hidden": false,
 				"optional": false,
-				"control": "text",
-				"type": "string",
+				"control": "field",
+				"type": "number",
 				"hint": {
 					"en_US": "Input data",
 					"zh_CN": "输入数据"
 				},
 				"label": {
-					"en_US": "Input data",
+					"en_US": "Data",
 					"zh_CN": "输入数据"
 				}
 			}
@@ -200,8 +196,8 @@
 			"category": "function",
 			"icon": "iconPath",
 			"label": {
-				"en_US": "geohashDecodeInt",
-				"zh_CN": "geohashDecodeInt"
+				"en_US": "Geohash Decode Integer",
+				"zh_CN": "Geohash 整数解码"
 			}
 		}
 	}, {
@@ -214,7 +210,7 @@
 		"args": [
 			{
 				"name": "data",
-				"hidden": true,
+				"hidden": false,
 				"optional": false,
 				"control": "text",
 				"type": "string",
@@ -223,7 +219,7 @@
 					"zh_CN": "输入数据"
 				},
 				"label": {
-					"en_US": "Input data",
+					"en_US": "Data",
 					"zh_CN": "输入数据"
 				}
 			}
@@ -239,8 +235,8 @@
 			"category": "function",
 			"icon": "iconPath",
 			"label": {
-				"en_US": "geohashBoundingBox",
-				"zh_CN": "geohashBoundingBox"
+				"en_US": "Geohash Bounding Box",
+				"zh_CN": "Geohash 边界框"
 			}
 		}
 	}, {
@@ -253,16 +249,16 @@
 		"args": [
 			{
 				"name": "data",
-				"hidden": true,
+				"hidden": false,
 				"optional": false,
-				"control": "text",
+				"control": "field",
 				"type": "string",
 				"hint": {
 					"en_US": "Input data",
 					"zh_CN": "输入数据"
 				},
 				"label": {
-					"en_US": "Input data",
+					"en_US": "Data",
 					"zh_CN": "输入数据"
 				}
 			}
@@ -278,8 +274,8 @@
 			"category": "function",
 			"icon": "iconPath",
 			"label": {
-				"en_US": "geohashBoundingBoxInt",
-				"zh_CN": "geohashBoundingBoxInt"
+				"en_US": "Geohash Integer Bounding Box",
+				"zh_CN": "Geohash 整数边界框"
 			}
 		}
 	}, {
@@ -292,32 +288,31 @@
 		"args": [
 			{
 				"name": "hash",
-				"hidden": true,
+				"hidden": false,
 				"optional": false,
-				"control": "text",
+				"control": "field",
 				"type": "string",
 				"hint": {
 					"en_US": "Input data",
 					"zh_CN": "输入数据"
 				},
 				"label": {
-					"en_US": "Input data",
+					"en_US": "Field",
 					"zh_CN": "输入数据"
 				}
 			},
 			{
 				"name": "direction",
-				"hidden": true,
 				"optional": false,
 				"control": "text",
 				"type": "string",
 				"hint": {
 					"en_US": "Input direction",
-					"zh_CN": "输入数据"
+					"zh_CN": "输入方向"
 				},
 				"label": {
-					"en_US": "Input direction",
-					"zh_CN": "输入数据"
+					"en_US": "Direction",
+					"zh_CN": "方向"
 				}
 			}
 		],
@@ -332,8 +327,8 @@
 			"category": "function",
 			"icon": "iconPath",
 			"label": {
-				"en_US": "geohashNeighbor",
-				"zh_CN": "geohashNeighbor"
+				"en_US": "Geohash Neighbor",
+				"zh_CN": "Geohash 相邻位置"
 			}
 		}
 	}, {
@@ -346,32 +341,31 @@
 		"args": [
 			{
 				"name": "hash",
-				"hidden": true,
+				"hidden": false,
 				"optional": false,
-				"control": "text",
+				"control": "field",
 				"type": "string",
 				"hint": {
 					"en_US": "Input data",
 					"zh_CN": "输入数据"
 				},
 				"label": {
-					"en_US": "Input data",
+					"en_US": "Data",
 					"zh_CN": "输入数据"
 				}
 			},
 			{
 				"name": "direction",
-				"hidden": true,
 				"optional": false,
 				"control": "text",
 				"type": "string",
 				"hint": {
 					"en_US": "Input direction",
-					"zh_CN": "输入数据"
+					"zh_CN": "输入方向"
 				},
 				"label": {
-					"en_US": "Input direction",
-					"zh_CN": "输入数据"
+					"en_US": "Direction",
+					"zh_CN": "方向"
 				}
 			}
 		],
@@ -386,8 +380,8 @@
 			"category": "function",
 			"icon": "iconPath",
 			"label": {
-				"en_US": "geohashNeighborInt",
-				"zh_CN": "geohashNeighborInt"
+				"en_US": "Geohash Neighbor Integer",
+				"zh_CN": "Geohash 相邻位置编码"
 			}
 		}
 	}, {
@@ -400,16 +394,16 @@
 		"args": [
 			{
 				"name": "hash",
-				"hidden": true,
+				"hidden": false,
 				"optional": false,
-				"control": "text",
+				"control": "field",
 				"type": "string",
 				"hint": {
 					"en_US": "Input data",
 					"zh_CN": "输入数据"
 				},
 				"label": {
-					"en_US": "Input data",
+					"en_US": "Field",
 					"zh_CN": "输入数据"
 				}
 			}
@@ -425,8 +419,8 @@
 			"category": "function",
 			"icon": "iconPath",
 			"label": {
-				"en_US": "geohashNeighbors",
-				"zh_CN": "geohashNeighbors"
+				"en_US": "Geohash All Neighbors",
+				"zh_CN": "Geohash 所有相邻位置"
 			}
 		}
 	}, {
@@ -438,7 +432,7 @@
 		},
 		"args": [{
 				"name": "hash",
-				"hidden": true,
+				"hidden": false,
 				"optional": false,
 				"control": "text",
 			"type": "string",
@@ -447,7 +441,7 @@
 				"zh_CN": "输入数据"
 			},
 			"label": {
-				"en_US": "Input data",
+				"en_US": "Field",
 				"zh_CN": "输入数据"
 			}
 		}
@@ -463,8 +457,8 @@
 			"category": "function",
 			"icon": "iconPath",
 			"label": {
-				"en_US": "geohashNeighbors",
-				"zh_CN": "geohashNeighbors"
+				"en_US": "Geohash All Neighbors Integer",
+				"zh_CN": "Geohash 所有相邻位置编码"
 			}
 		}
 	}]
diff --git a/extensions/functions/image/image.json b/extensions/functions/image/image.json
index 334f5f3c..5e05700f 100644
--- a/extensions/functions/image/image.json
+++ b/extensions/functions/image/image.json
@@ -28,21 +28,20 @@
 		"args": [
 			{
 				"name": "image",
-				"hidden": true,
+				"hidden": false,
 				"optional": false,
-				"control": "text",
+				"control": "field",
 				"type": "string",
 				"hint": {
-					"en_US": "Input data",
-					"zh_CN": "输入数据"
+					"en_US": "Input image",
+					"zh_CN": "输入图像"
 				},
 				"label": {
-					"en_US": "Input data",
-					"zh_CN": "输入数据"
+					"en_US": "Image",
+					"zh_CN": "图像"
 				}
 			},{
 				"name": "width",
-				"hidden": true,
 				"optional": false,
 				"control": "text",
 				"type": "int",
@@ -51,12 +50,11 @@
 					"zh_CN": "输入宽度"
 				},
 				"label": {
-					"en_US": "Input width",
-					"zh_CN": "输入宽度"
+					"en_US": "Width",
+					"zh_CN": "宽度"
 				}
 			},{
 				"name": "height",
-				"hidden": true,
 				"optional": false,
 				"control": "text",
 				"type": "int",
@@ -65,12 +63,11 @@
 					"zh_CN": "输入高度"
 				},
 				"label": {
-					"en_US": "Input height",
-					"zh_CN": "输入高度"
+					"en_US": "Height",
+					"zh_CN": "高度"
 				}
 			},{
 				"name": "isRaw",
-				"hidden": true,
 				"optional": true,
 				"control": "radio",
 				"type": "bool",
@@ -95,8 +92,8 @@
 			"category": "function",
 			"icon": "iconPath",
 			"label": {
-				"en_US": "resize",
-				"zh_CN": "resize"
+				"en_US": "Image Resize",
+				"zh_CN": "图像调整大小"
 			}
 		}
 	}, {
@@ -109,21 +106,20 @@
 		"args": [
 			{
 				"name": "image",
-				"hidden": true,
+				"hidden": false,
 				"optional": false,
-				"control": "text",
+				"control": "field",
 				"type": "string",
 				"hint": {
-					"en_US": "Input data",
-					"zh_CN": "输入数据"
+					"en_US": "Input image",
+					"zh_CN": "输入图像"
 				},
 				"label": {
-					"en_US": "Input data",
-					"zh_CN": "输入数据"
+					"en_US": "Image",
+					"zh_CN": "图像"
 				}
 			},{
 				"name": "maxWidth",
-				"hidden": true,
 				"optional": false,
 				"control": "text",
 				"type": "int",
@@ -132,12 +128,11 @@
 					"zh_CN": "输入最大宽度"
 				},
 				"label": {
-					"en_US": "Input maxWidth",
-					"zh_CN": "输入最大宽度"
+					"en_US": "Max Width",
+					"zh_CN": "最大宽度"
 				}
 			},{
 				"name": "maxHeight",
-				"hidden": true,
 				"optional": false,
 				"control": "text",
 				"type": "int",
@@ -146,8 +141,8 @@
 					"zh_CN": "输入最大高度"
 				},
 				"label": {
-					"en_US": "Input maxHeight",
-					"zh_CN": "输入最大高度"
+					"en_US": "MaxHeight",
+					"zh_CN": "最大高度"
 				}
 			}
 		],
@@ -162,8 +157,8 @@
 			"category": "function",
 			"icon": "iconPath",
 			"label": {
-				"en_US": "thumbnail",
-				"zh_CN": "thumbnail"
+				"en_US": "Thumbnail",
+				"zh_CN": "缩略图"
 			}
 		}
 	}]
diff --git a/extensions/functions/labelImage/labelImage.json b/extensions/functions/labelImage/labelImage.json
index 459362f2..513857c5 100644
--- a/extensions/functions/labelImage/labelImage.json
+++ b/extensions/functions/labelImage/labelImage.json
@@ -12,12 +12,10 @@
           "zh_CN": "https://ekuiper.org/docs/zh/latest/sqls/custom_functions.html"
         },
 		"description": {
-			"en_US": "",
-			"zh_CN": ""
+			"en_US": "Example plugin to demonstrate inferring Tensorflow lite model to label an image",
+			"zh_CN": "示例插件，演示如何使用Tensorflow lite模型对图像进行标记推断。"
 		}
 	},
-	"libs": [],
-	"name": "labelImage",
 	"functions": [{
 		"name": "labelImage",
 		"example": "labelImage(col1)",
@@ -27,18 +25,18 @@
 		},
 		"args": [
 			{
-				"name": "inputData",
-				"hidden": true,
+				"name": "image",
+				"hidden": false,
 				"optional": false,
-				"control": "text",
+				"control": "field",
 				"type": "string",
 				"hint": {
-					"en_US": "Input data",
-					"zh_CN": "输入数据"
+					"en_US": "Input image",
+					"zh_CN": "输入图像"
 				},
 				"label": {
-					"en_US": "Input data",
-					"zh_CN": "输入数据"
+					"en_US": "Image",
+					"zh_CN": "图像"
 				}
 			}
 		],
@@ -53,8 +51,8 @@
 			"category": "function",
 			"icon": "iconPath",
 			"label": {
-				"en_US": "labelImage",
-				"zh_CN": "labelImage"
+				"en_US": "Label Image",
+				"zh_CN": "图像标注"
 			}
 		}
 	}]
diff --git a/extensions/functions/tfLite/tfLite.json b/extensions/functions/tfLite/tfLite.json
index 68f65f14..ae60ccc9 100644
--- a/extensions/functions/tfLite/tfLite.json
+++ b/extensions/functions/tfLite/tfLite.json
@@ -12,23 +12,21 @@
 			"zh_CN": "https://ekuiper.org/docs/zh/latest/sqls/custom_functions.html"
 		},
 		"description": {
-			"en_US": "",
-			"zh_CN": ""
+			"en_US": "General Tensorflow lite plugin which can infer any tflite model dynamically",
+			"zh_CN": "通用的 TensorFlow Lite 插件，可以动态推断任何 tflite 模型。"
 		}
 	},
-	"libs": [],
-	"name": "tfLite",
 	"functions": [{
 		"name": "tfLite",
 		"example": "tfLite(model,para1, para2,...)",
 		"hint": {
-			"en_US": "Creates a scaled image with new dimensions (width, height) .If either width or height is set to 0, it will be set to an aspect ratio preserving value.",
-			"zh_CN": "创建具有新尺寸（宽度，高度）的缩放图像。如果width或height设置为0，则将其设置为长宽比保留值。"
+			"en_US": "Select AI model in runtime and infer the stream data",
+			"zh_CN": "动态选择模型进行推断"
 		},
 		"args": [
 			{
 				"name": "model",
-				"hidden": true,
+				"hidden": false,
 				"optional": false,
 				"control": "text",
 				"type": "string",
@@ -37,36 +35,22 @@
 					"zh_CN": "输入模型"
 				},
 				"label": {
-					"en_US": "Input data",
-					"zh_CN": "输入模型"
-				}
-			},{
-				"name": "para1",
-				"hidden": true,
-				"optional": false,
-				"control": "text",
-				"type": "int",
-				"hint": {
-					"en_US": "Input data",
-					"zh_CN": "输入数据1"
-				},
-				"label": {
-					"en_US": "Input data",
-					"zh_CN": "输入数据1"
+					"en_US": "Model Name",
+					"zh_CN": "模型名称"
 				}
 			},{
-				"name": "para2",
-				"hidden": true,
+				"name": "fields",
+				"default": "",
 				"optional": false,
-				"control": "text",
-				"type": "int",
+				"control": "list",
+				"type": "list_string",
 				"hint": {
-					"en_US": "Input height",
-					"zh_CN": "输入数据2"
+					"en_US": "select parameter fields",
+					"zh_CN": "选取参数字段"
 				},
 				"label": {
-					"en_US": "Input height",
-					"zh_CN": "输入数据2"
+					"en_US": "Parameter Fields",
+					"zh_CN": "参数字段"
 				}
 			}
 		],
@@ -81,8 +65,8 @@
 			"category": "function",
 			"icon": "iconPath",
 			"label": {
-				"en_US": "tflite",
-				"zh_CN": "tflite"
+				"en_US": "Tensorflow Lite",
+				"zh_CN": "Tensorflow Lite"
 			}
 		}
 	}]
diff --git a/extensions/sinks/file/file.json b/extensions/sinks/file/file.json
index 95b1079e..d6105e00 100644
--- a/extensions/sinks/file/file.json
+++ b/extensions/sinks/file/file.json
@@ -8,8 +8,8 @@
 			"website": "https://www.emqx.io"
 		},
 		"helpUrl": {
-			"en_US": "https://ekuiper.org/docs/en/latest/guide/sinks/plugin/file.html",
-			"zh_CN": "https://ekuiper.org/docs/zh/latest/guide/sinks/plugin/file.html"
+			"en_US": "https://ekuiper.org/docs/en/latest/guide/sinks/builtin/file.html",
+			"zh_CN": "https://ekuiper.org/docs/zh/latest/guide/sinks/builtin/file.html"
 		},
 		"description": {
 			"en_US": "This a sink plugin for file, it can be used for saving the analysis data into file system.",
diff --git a/extensions/sinks/image/image.json b/extensions/sinks/image/image.json
index f6748dc5..50ddaee6 100644
--- a/extensions/sinks/image/image.json
+++ b/extensions/sinks/image/image.json
@@ -8,12 +8,12 @@
 			"website": "https://www.emqx.io"
 		},
 		"helpUrl": {
-          "en_US": "https://ekuiper.org/docs/en/latest/guide/sinks/plugin/file.html",
-          "zh_CN": "https://ekuiper.org/docs/zh/latest/guide/sinks/plugin/file.html"
+          "en_US": "https://ekuiper.org/docs/en/latest/guide/sinks/plugin/image.html",
+          "zh_CN": "https://ekuiper.org/docs/zh/latest/guide/sinks/plugin/image.html"
         },
 		"description": {
-			"en_US": "This a sink plugin for file, it can be used for saving the analysis data into file system.",
-			"zh_CN": "本插件为文件持久化插件，可以用于将分析数据存入指定的文件中。"
+			"en_US": "This sink is used to save the picture to the specified folder.",
+			"zh_CN": "本插件用于将图片保存到指定文件夹。"
 		}
 	},
 	"libs": [
@@ -78,5 +78,13 @@
 			"en_US": "maxCount",
 			"zh_CN": "最大写入数量"
 		}
-	}]
+	}],
+	"node": {
+		"category": "sink",
+		"icon": "iconPath",
+		"label": {
+			"en": "Image",
+			"zh": "图像"
+		}
+	}
 }
diff --git a/extensions/sinks/influx/influx.json b/extensions/sinks/influx/influx.json
index 6e6ffdf1..c870d3c4 100644
--- a/extensions/sinks/influx/influx.json
+++ b/extensions/sinks/influx/influx.json
@@ -8,8 +8,8 @@
 			"website": ""
 		},
 		"helpUrl": {
-          "en_US": "https://ekuiper.org/docs/en/latest/guide/sinks/plugin/influxdb.html",
-          "zh_CN": "https://ekuiper.org/docs/zh/latest/guide/sinks/plugin/influxdb.html"
+          "en_US": "https://ekuiper.org/docs/en/latest/guide/sinks/plugin/influx.html",
+          "zh_CN": "https://ekuiper.org/docs/zh/latest/guide/sinks/plugin/influx.html"
         },
 		"description": {
 			"en_US": "This a sink plugin for InfluxDB, it can be used for saving the analysis data into InfluxDB.",
@@ -133,11 +133,11 @@
 		}
 	}],
 	"node": {
-		"category": "Sink",
+		"category": "sink",
 		"icon": "iconPath",
 		"label": {
-			"en": "Influx",
-			"zh": "Influx"
+			"en": "InfluxDB 1",
+			"zh": "InfluxDB 1"
 		}
 	}
 }
diff --git a/extensions/sinks/influx2/influx2.json b/extensions/sinks/influx2/influx2.json
index ae14257c..258206c9 100644
--- a/extensions/sinks/influx2/influx2.json
+++ b/extensions/sinks/influx2/influx2.json
@@ -131,5 +131,13 @@
 			"en_US": "Column",
 			"zh_CN": "列名"
 		}
-	}]
+	}],
+	"node": {
+		"category": "sink",
+		"icon": "iconPath",
+		"label": {
+			"en": "InfluxDB 2",
+			"zh": "InfluxDB 2"
+		}
+	}
 }
diff --git a/extensions/sinks/sql/sql.json b/extensions/sinks/sql/sql.json
index d22bfc7d..75d7870c 100644
--- a/extensions/sinks/sql/sql.json
+++ b/extensions/sinks/sql/sql.json
@@ -92,7 +92,7 @@
 	}
  ],
 	"node": {
-		"category": "Sink",
+		"category": "sink",
 		"icon": "iconPath",
 		"label": {
 			"en": "SQL",
diff --git a/extensions/sinks/tdengine/tdengine.json b/extensions/sinks/tdengine/tdengine.json
index 4cf7147e..9d1d7a2a 100644
--- a/extensions/sinks/tdengine/tdengine.json
+++ b/extensions/sinks/tdengine/tdengine.json
@@ -201,11 +201,11 @@
     }
   ],
   "node": {
-    "category": "Sink",
+    "category": "sink",
     "icon": "iconPath",
     "label": {
-      "en": "Tdengine",
-      "zh": "Tdengine"
+      "en": "TDengine 2",
+      "zh": "TDengine 2"
     }
   }
 }
diff --git a/extensions/sinks/zmq/zmq.json b/extensions/sinks/zmq/zmq.json
index d41a37e3..07c03e76 100644
--- a/extensions/sinks/zmq/zmq.json
+++ b/extensions/sinks/zmq/zmq.json
@@ -49,11 +49,11 @@
 		}
 	}],
 	"node": {
-		"category": "Sink",
+		"category": "sink",
 		"icon": "iconPath",
 		"label": {
-			"en": "ZMQ",
-			"zh": "ZMQ"
+			"en": "ZeroMQ",
+			"zh": "ZeroMQ"
 		}
 	}
 }
diff --git a/extensions/sources/zmq/zmq.json b/extensions/sources/zmq/zmq.json
index ad87aa6d..b9dbadf9 100644
--- a/extensions/sources/zmq/zmq.json
+++ b/extensions/sources/zmq/zmq.json
@@ -74,11 +74,11 @@
     }
   ],
   "node": {
-    "category": ["Protocol","Protocol1"],
+    "category": "source",
     "icon": "iconPath",
     "label": {
-      "en_US": "ZMQ",
-      "zh_CN": "ZMQ"
+      "en_US": "ZeroMQ",
+      "zh_CN": "ZeroMQ"
     }
   }
 }
diff --git a/internal/conf/conf.go b/internal/conf/conf.go
index 4ef13a1c..2fbc3526 100644
--- a/internal/conf/conf.go
+++ b/internal/conf/conf.go
@@ -1,4 +1,4 @@
-// Copyright 2021-2022 EMQ Technologies Co., Ltd.
+// Copyright 2021-2023 EMQ Technologies Co., Ltd.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -147,7 +147,8 @@ type KuiperConf struct {
 		}
 	}
 	Portable struct {
-		PythonBin string `yaml:"pythonBin"`
+		PythonBin   string `yaml:"pythonBin"`
+		InitTimeout int    `yaml:"initTimeout"`
 	}
 }
 
@@ -226,6 +227,9 @@ func InitConf() {
 	if Config.Portable.PythonBin == "" {
 		Config.Portable.PythonBin = "python"
 	}
+	if Config.Portable.InitTimeout <= 0 {
+		Config.Portable.InitTimeout = 5000
+	}
 	if Config.Source == nil {
 		Config.Source = &SourceConf{}
 	}
diff --git a/internal/plugin/portable/factory.go b/internal/plugin/portable/factory.go
index 1d4503c5..7592b0de 100644
--- a/internal/plugin/portable/factory.go
+++ b/internal/plugin/portable/factory.go
@@ -43,6 +43,10 @@ func (m *Manager) Sink(name string) (api.Sink, error) {
 	return runtime.NewPortableSink(name, meta), nil
 }
 
+// The function instance are kept forever even after deletion
+// The instance is actually a wrapper of the nng channel which is dependant from the plugin instance
+// Even updated plugin instance can reuse the channel if the function name is not changed
+// It is not used to check if the function is bound, use ConvName which checks the meta
 var funcInsMap = &sync.Map{}
 
 func (m *Manager) Function(name string) (api.Function, error) {
@@ -56,7 +60,7 @@ func (m *Manager) Function(name string) (api.Function, error) {
 	}
 	f, err := runtime.NewPortableFunc(name, meta)
 	if err != nil {
-		conf.Log.Errorf("Error creating function %v", err)
+		conf.Log.Errorf("Error creating portable function %v", err)
 		return nil, err
 	}
 	funcInsMap.Store(name, f)
@@ -72,21 +76,3 @@ func (m *Manager) ConvName(funcName string) (string, bool) {
 	_, ok := m.GetPluginMeta(plugin.FUNCTION, funcName)
 	return funcName, ok
 }
-
-// Clean up function map
-func (m *Manager) Clean() {
-	funcInsMap.Range(func(_, ins interface{}) bool {
-		f := ins.(*runtime.PortableFunc)
-		_ = f.Close()
-		return true
-	})
-	funcInsMap = &sync.Map{}
-}
-
-func (m *Manager) DeleteFunc(funcName string) {
-	f, ok := funcInsMap.Load(funcName)
-	if ok {
-		_ = f.(*runtime.PortableFunc).Close()
-		funcInsMap.Delete(funcName)
-	}
-}
diff --git a/internal/plugin/portable/manager.go b/internal/plugin/portable/manager.go
index 570705e2..f456d38b 100644
--- a/internal/plugin/portable/manager.go
+++ b/internal/plugin/portable/manager.go
@@ -388,7 +388,6 @@ func (m *Manager) Delete(name string) error {
 	for _, s := range pinfo.Functions {
 		p := path.Join(m.pluginConfDir, plugin.PluginTypes[plugin.FUNCTION], s+".json")
 		os.Remove(p)
-		m.DeleteFunc(s)
 	}
 	_ = os.RemoveAll(path.Join(m.pluginDir, name))
 	m.removePluginInstallScript(name)
diff --git a/internal/plugin/portable/runtime/connection.go b/internal/plugin/portable/runtime/connection.go
index cbdc0277..95ee3a04 100644
--- a/internal/plugin/portable/runtime/connection.go
+++ b/internal/plugin/portable/runtime/connection.go
@@ -1,4 +1,4 @@
-// Copyright 2021-2022 EMQ Technologies Co., Ltd.
+// Copyright 2021-2023 EMQ Technologies Co., Ltd.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -89,7 +89,7 @@ func (r *NanomsgReqChannel) Handshake() error {
 	if err != nil {
 		return err
 	}
-	err = r.sock.SetOption(mangos.OptionRecvDeadline, 5*time.Second)
+	err = r.sock.SetOption(mangos.OptionRecvDeadline, time.Duration(conf.Config.Portable.InitTimeout)*time.Millisecond)
 	if err != nil {
 		return err
 	}
@@ -131,22 +131,35 @@ func (r *NanomsgReqRepChannel) Close() error {
 func (r *NanomsgReqRepChannel) Req(arg []byte) ([]byte, error) {
 	r.Lock()
 	defer r.Unlock()
-	if err := r.sock.Send(arg); err != nil {
-		if err == mangos.ErrProtoState { // resend if protocol state wrong, because of plugin restart or other problems
-			prev, err := r.sock.Recv()
+	conf.Log.Debugf("send request: %s", string(arg))
+	for {
+		err := r.sock.Send(arg)
+		// resend if protocol state wrong, because of plugin restart or other problems
+		if err == mangos.ErrProtoState {
+			conf.Log.Debugf("send request protestate error %s", err.Error())
+			var prev []byte
+			prev, err = r.sock.Recv()
 			if err == nil {
 				conf.Log.Warnf("discard previous response: %s", string(prev))
+				conf.Log.Debugf("resend request: %s", string(arg))
 				err = r.sock.Send(arg)
-				if err == nil {
-					return r.sock.Recv()
-				}
-			} else {
-				return nil, fmt.Errorf("can't send message on control rep socket: %s", err.Error())
 			}
 		}
-		return nil, fmt.Errorf("can't send message on function rep socket: %s", err.Error())
+		if err != nil {
+			return nil, fmt.Errorf("can't send message on function rep socket: %s", err.Error())
+		}
+		result, e := r.sock.Recv()
+		if e != nil {
+			conf.Log.Errorf("can't receive: %s", e.Error())
+		} else {
+			conf.Log.Debugf("receive response: %s", string(result))
+		}
+		if len(result) > 0 && result[0] == 'h' {
+			conf.Log.Debugf("receive handshake response: %s", string(result))
+			continue
+		}
+		return result, e
 	}
-	return r.sock.Recv()
 }
 
 func CreateSourceChannel(ctx api.StreamContext) (DataInChannel, error) {
@@ -242,13 +255,13 @@ func setSockOptions(sock mangos.Socket, sockOptions map[string]interface{}) {
 
 func listenWithRetry(sock mangos.Socket, url string) error {
 	var (
-		retryCount    = 300
+		retryCount    = 5
 		retryInterval = 100
 	)
 	for {
 		err := sock.Listen(url)
 		if err == nil {
-			conf.Log.Infof("start to listen at %s after %d tries", url, 301-retryCount)
+			conf.Log.Infof("start to listen at %s after %d tries", url, 5-retryCount)
 			return err
 		}
 		retryCount--
diff --git a/internal/plugin/portable/runtime/function.go b/internal/plugin/portable/runtime/function.go
index 89a9440e..94d4ba1d 100644
--- a/internal/plugin/portable/runtime/function.go
+++ b/internal/plugin/portable/runtime/function.go
@@ -1,4 +1,4 @@
-// Copyright 2022 EMQ Technologies Co., Ltd.
+// Copyright 2022-2023 EMQ Technologies Co., Ltd.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -23,30 +23,39 @@ import (
 )
 
 // PortableFunc each function symbol only has a singleton
-// Each singleton are long running go routine.
+// Each singleton are long-running go routine
+// Currently, it is cached and never ended once created
+// It is actually a wrapper of the data channel and can be fit to any plugin instance
+// Thus, it is possible to hot reload, which is simply attach a new nng client to the same channel
+// without changing the server(plugin runtime) side
 // TODO think about ending a portable func when needed.
 type PortableFunc struct {
 	symbolName string
-	reg        *PluginMeta
+	reg        *PluginMeta // initial plugin meta, only used for initialize the function instance
 	dataCh     DataReqChannel
 	isAgg      int // 0 - not calculate yet, 1 - no, 2 - yes
 }
 
-func NewPortableFunc(symbolName string, reg *PluginMeta) (*PortableFunc, error) {
+func NewPortableFunc(symbolName string, reg *PluginMeta) (_ *PortableFunc, e error) {
 	// Setup channel and route the data
 	conf.Log.Infof("Start running portable function meta %+v", reg)
 	pm := GetPluginInsManager()
-	ins, err := pm.getOrStartProcess(reg, PortbleConf, false)
+	ins, err := pm.getOrStartProcess(reg, PortbleConf)
 	if err != nil {
 		return nil, err
 	}
-	conf.Log.Infof("Plugin started successfully")
 
 	// Create function channel
+	conf.Log.Infof("creating function channel for symbol %s", symbolName)
 	dataCh, err := CreateFunctionChannel(symbolName)
 	if err != nil {
 		return nil, err
 	}
+	defer func() {
+		if e != nil {
+			dataCh.Close()
+		}
+	}()
 
 	// Start symbol
 	c := &Control{
@@ -54,6 +63,7 @@ func NewPortableFunc(symbolName string, reg *PluginMeta) (*PortableFunc, error)
 		PluginType: TYPE_FUNC,
 	}
 	ctx := kctx.WithValue(kctx.Background(), kctx.LoggerKey, conf.Log)
+	conf.Log.Infof("starting symbol %s", symbolName)
 	err = ins.StartSymbol(ctx, c)
 	if err != nil {
 		return nil, err
@@ -104,16 +114,8 @@ func (f *PortableFunc) Exec(args []interface{}, ctx api.FunctionContext) (interf
 	}
 	fr := &FuncReply{}
 	err = json.Unmarshal(res, fr)
-	if err != nil { // retry if receive handshake after restart function process
-		ctx.GetLogger().Warnf("Failed to unmarshal function result %s", string(res))
-		res, err = f.dataCh.Req(jsonArg)
-		if err != nil {
-			return err, false
-		}
-		err = json.Unmarshal(res, fr)
-		if err != nil {
-			return err, false
-		}
+	if err != nil {
+		return fmt.Errorf("Failed to unmarshal function result %s", string(res)), false
 	}
 	if !fr.State {
 		if fr.Result != nil {
diff --git a/internal/plugin/portable/runtime/plugin_ins_manager.go b/internal/plugin/portable/runtime/plugin_ins_manager.go
index 1a50bebf..e5f4af99 100644
--- a/internal/plugin/portable/runtime/plugin_ins_manager.go
+++ b/internal/plugin/portable/runtime/plugin_ins_manager.go
@@ -72,6 +72,14 @@ func NewPluginInsForTest(name string, ctrlChan ControlChannel) *PluginIns {
 	}
 }
 
+func (i *PluginIns) sendCmd(jsonArg []byte) error {
+	err := i.ctrlChan.SendCmd(jsonArg)
+	if err != nil && i.process == nil {
+		return fmt.Errorf("plugin %s is not running sucessfully, please make sure it is valid", i.name)
+	}
+	return err
+}
+
 func (i *PluginIns) StartSymbol(ctx api.StreamContext, ctrl *Control) error {
 	arg, err := json.Marshal(ctrl)
 	if err != nil {
@@ -85,7 +93,7 @@ func (i *PluginIns) StartSymbol(ctx api.StreamContext, ctrl *Control) error {
 	if err != nil {
 		return err
 	}
-	err = i.ctrlChan.SendCmd(jsonArg)
+	err = i.sendCmd(jsonArg)
 	if err == nil {
 		i.Lock()
 		i.commands[ctrl.Meta] = jsonArg
@@ -108,7 +116,7 @@ func (i *PluginIns) StopSymbol(ctx api.StreamContext, ctrl *Control) error {
 	if err != nil {
 		return err
 	}
-	err = i.ctrlChan.SendCmd(jsonArg)
+	err = i.sendCmd(jsonArg)
 	if err == nil {
 		referred := false
 		i.Lock()
@@ -181,7 +189,7 @@ func (p *pluginInsManager) CreateIns(pluginMeta *PluginMeta) {
 	defer p.Unlock()
 	if ins, ok := p.instances[pluginMeta.Name]; ok {
 		if len(ins.commands) != 0 {
-			go p.getOrStartProcess(pluginMeta, PortbleConf, true)
+			go p.getOrStartProcess(pluginMeta, PortbleConf)
 		}
 	}
 }
@@ -193,7 +201,7 @@ func (p *pluginInsManager) CreateIns(pluginMeta *PluginMeta) {
 // During plugin delete/update, if the commands is not empty, keep the ins for next creation and restore
 // 1. During creation, clean up those resources for any errors in defer immediately after the resource is created.
 // 2. During plugin running, when detecting plugin process exit, clean up those resources for the current ins.
-func (p *pluginInsManager) getOrStartProcess(pluginMeta *PluginMeta, pconf *PortableConfig, pluginCreation bool) (*PluginIns, error) {
+func (p *pluginInsManager) getOrStartProcess(pluginMeta *PluginMeta, pconf *PortableConfig) (_ *PluginIns, e error) {
 	p.Lock()
 	defer p.Unlock()
 	var (
@@ -207,8 +215,7 @@ func (p *pluginInsManager) getOrStartProcess(pluginMeta *PluginMeta, pconf *Port
 		p.instances[pluginMeta.Name] = ins
 	}
 	// ins process has not run yet
-	if !pluginCreation && ins.ctrlChan != nil {
-		conf.Log.Warnf("plugin %s is not run yet", pluginMeta.Name)
+	if ins.process != nil && ins.ctrlChan != nil {
 		return ins, nil
 	}
 	// should only happen for first start, then the ctrl channel will keep running
@@ -218,11 +225,6 @@ func (p *pluginInsManager) getOrStartProcess(pluginMeta *PluginMeta, pconf *Port
 		if err != nil {
 			return nil, fmt.Errorf("can't create new control channel: %s", err.Error())
 		}
-		defer func() {
-			if err != nil {
-				_ = ctrlChan.Close()
-			}
-		}()
 		ins.ctrlChan = ctrlChan
 	}
 	// init or restart all need to run the process
@@ -255,12 +257,12 @@ func (p *pluginInsManager) getOrStartProcess(pluginMeta *PluginMeta, pconf *Port
 	conf.Log.Println("plugin starting")
 	err = cmd.Start()
 	if err != nil {
-		return nil, fmt.Errorf("plugin executable %s starts with error %v", pluginMeta.Executable, err)
+		return nil, fmt.Errorf("plugin executable %s stops with error %v", pluginMeta.Executable, err)
 	}
 	process := cmd.Process
 	conf.Log.Printf("plugin started pid: %d\n", process.Pid)
 	defer func() {
-		if err != nil {
+		if e != nil {
 			_ = process.Kill()
 		}
 	}()
@@ -296,7 +298,7 @@ func (p *pluginInsManager) getOrStartProcess(pluginMeta *PluginMeta, pconf *Port
 	conf.Log.Info("restore plugin symbols")
 	for m, c := range ins.commands {
 		go func(key Meta, jsonArg []byte) {
-			e := ins.ctrlChan.SendCmd(jsonArg)
+			e := ins.sendCmd(jsonArg)
 			if e != nil {
 				conf.Log.Errorf("send command to %v error: %v", key, e)
 			}
diff --git a/internal/plugin/portable/runtime/sink.go b/internal/plugin/portable/runtime/sink.go
index e8ecaceb..d94c7833 100644
--- a/internal/plugin/portable/runtime/sink.go
+++ b/internal/plugin/portable/runtime/sink.go
@@ -1,4 +1,4 @@
-// Copyright 2021-2022 EMQ Technologies Co., Ltd.
+// Copyright 2021-2023 EMQ Technologies Co., Ltd.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -43,7 +43,7 @@ func (ps *PortableSink) Configure(props map[string]interface{}) error {
 func (ps *PortableSink) Open(ctx api.StreamContext) error {
 	ctx.GetLogger().Infof("Start running portable sink %s with conf %+v", ps.symbolName, ps.props)
 	pm := GetPluginInsManager()
-	ins, err := pm.getOrStartProcess(ps.reg, PortbleConf, false)
+	ins, err := pm.getOrStartProcess(ps.reg, PortbleConf)
 	if err != nil {
 		return err
 	}
diff --git a/internal/plugin/portable/runtime/source.go b/internal/plugin/portable/runtime/source.go
index 5346c926..8ccedcef 100644
--- a/internal/plugin/portable/runtime/source.go
+++ b/internal/plugin/portable/runtime/source.go
@@ -1,4 +1,4 @@
-// Copyright 2021-2022 EMQ Technologies Co., Ltd.
+// Copyright 2021-2023 EMQ Technologies Co., Ltd.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -44,7 +44,7 @@ func NewPortableSource(symbolName string, reg *PluginMeta) *PortableSource {
 func (ps *PortableSource) Open(ctx api.StreamContext, consumer chan<- api.SourceTuple, errCh chan<- error) {
 	ctx.GetLogger().Infof("Start running portable source %s with datasource %s and conf %+v", ps.symbolName, ps.topic, ps.props)
 	pm := GetPluginInsManager()
-	ins, err := pm.getOrStartProcess(ps.reg, PortbleConf, false)
+	ins, err := pm.getOrStartProcess(ps.reg, PortbleConf)
 	if err != nil {
 		infra.DrainError(ctx, err, errCh)
 		return
diff --git a/internal/processor/ruleset.go b/internal/processor/ruleset.go
index ad39f75b..3c4f43fc 100644
--- a/internal/processor/ruleset.go
+++ b/internal/processor/ruleset.go
@@ -1,4 +1,4 @@
-// Copyright 2022-2023 EMQ Technologies Co., Ltd.
+// Copyright 2022 EMQ Technologies Co., Ltd.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -146,6 +146,7 @@ func (rs *RulesetProcessor) ImportRuleSet(all Ruleset) {
 	_ = rs.s.tableStatusDb.Clean()
 	_ = rs.r.ruleStatusDb.Clean()
 
+	counts := make([]int, 3)
 	// restore streams
 	for k, v := range all.Streams {
 		_, e := rs.s.ExecStreamSql(v)
@@ -153,6 +154,8 @@ func (rs *RulesetProcessor) ImportRuleSet(all Ruleset) {
 			conf.Log.Errorf("Fail to import stream %s(%s) with error: %v", k, v, e)
 			_ = rs.s.streamStatusDb.Set(k, e.Error())
 			continue
+		} else {
+			counts[0]++
 		}
 	}
 	// restore tables
@@ -162,6 +165,8 @@ func (rs *RulesetProcessor) ImportRuleSet(all Ruleset) {
 			conf.Log.Errorf("Fail to import table %s(%s) with error: %v", k, v, e)
 			_ = rs.s.tableStatusDb.Set(k, e.Error())
 			continue
+		} else {
+			counts[1]++
 		}
 	}
 	var rules []string
@@ -174,6 +179,7 @@ func (rs *RulesetProcessor) ImportRuleSet(all Ruleset) {
 			continue
 		} else {
 			rules = append(rules, k)
+			counts[2]++
 		}
 	}
 }
diff --git a/internal/server/rest.go b/internal/server/rest.go
index dc1f02e3..9738600a 100644
--- a/internal/server/rest.go
+++ b/internal/server/rest.go
@@ -1,4 +1,4 @@
-// Copyright 2021-2022 EMQ Technologies Co., Ltd.
+// Copyright 2021-2023 EMQ Technologies Co., Ltd.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -450,7 +450,7 @@ func ruleHandler(w http.ResponseWriter, r *http.Request) {
 	case http.MethodGet:
 		rule, err := ruleProcessor.GetRuleJson(name)
 		if err != nil {
-			handleError(w, err, "describe rule error", logger)
+			handleError(w, err, "Describe rule error", logger)
 			return
 		}
 		w.Header().Add(ContentType, ContentTypeJSON)
@@ -459,7 +459,7 @@ func ruleHandler(w http.ResponseWriter, r *http.Request) {
 		deleteRule(name)
 		content, err := ruleProcessor.ExecDrop(name)
 		if err != nil {
-			handleError(w, err, "delete rule error", logger)
+			handleError(w, err, "Delete rule error", logger)
 			return
 		}
 		w.WriteHeader(http.StatusOK)
@@ -467,7 +467,7 @@ func ruleHandler(w http.ResponseWriter, r *http.Request) {
 	case http.MethodPut:
 		_, err := ruleProcessor.GetRuleById(name)
 		if err != nil {
-			handleError(w, err, "not found this rule", logger)
+			handleError(w, err, "Rule not found", logger)
 			return
 		}
 
@@ -478,7 +478,7 @@ func ruleHandler(w http.ResponseWriter, r *http.Request) {
 		}
 		err = updateRule(name, string(body))
 		if err != nil {
-			handleError(w, err, "restart rule error", logger)
+			handleError(w, err, "Update rule error", logger)
 			return
 		}
 		// Update to db after validation
@@ -751,7 +751,7 @@ func configurationImport(data []byte, reboot bool) error {
 	rulesetProcessor.ImportRuleSet(ruleSet)
 	if !reboot {
 		infra.SafeRun(func() error {
-			for name, _ := range ruleSet.Rules {
+			for name := range ruleSet.Rules {
 				rul, ee := ruleProcessor.GetRuleById(name)
 				if ee != nil {
 					logger.Error(ee)
diff --git a/internal/server/rule_manager.go b/internal/server/rule_manager.go
index 531f2f2f..b7f99107 100644
--- a/internal/server/rule_manager.go
+++ b/internal/server/rule_manager.go
@@ -1,4 +1,4 @@
-// Copyright 2021-2022 EMQ Technologies Co., Ltd.
+// Copyright 2021-2023 EMQ Technologies Co., Ltd.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -146,7 +146,10 @@ func updateRule(ruleId, ruleJson string) error {
 		return fmt.Errorf("Invalid rule json: %v", err)
 	}
 	if rs, ok := registry.Load(r.Id); ok {
-		rs.UpdateTopo(r)
+		err := rs.UpdateTopo(r)
+		if err != nil {
+			return err
+		}
 		err = ruleProcessor.ExecReplaceRuleState(rs.RuleId, true)
 		return err
 	} else {
diff --git a/internal/topo/operator/filter_operator.go b/internal/topo/operator/filter_operator.go
index a882e353..337bb7a2 100644
--- a/internal/topo/operator/filter_operator.go
+++ b/internal/topo/operator/filter_operator.go
@@ -1,4 +1,4 @@
-// Copyright 2021-2022 EMQ Technologies Co., Ltd.
+// Copyright 2021-2023 EMQ Technologies Co., Ltd.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -74,9 +74,6 @@ func (p *FilterOp) Apply(ctx api.StreamContext, data interface{}, fv *xsql.Funct
 			return err
 		}
 		r := input.Filter(sel)
-		if r.Len() == 0 {
-			return nil
-		}
 		return r
 	default:
 		return fmt.Errorf("run Where error: invalid input %[1]T(%[1]v)", input)
diff --git a/sdk/go/example/mirror/functions/echo.json b/sdk/go/example/mirror/functions/echo.json
index 46252dc0..7c3db220 100644
--- a/sdk/go/example/mirror/functions/echo.json
+++ b/sdk/go/example/mirror/functions/echo.json
@@ -8,53 +8,52 @@
 			"website": "https://www.emqx.io"
 		},
 		"helpUrl": {
-			"en_US": "https://ekuiper.org/docs/en/latest/sqls/custom_functions.html",
-			"zh_CN": "https://ekuiper.org/docs/zh/latest/sqls/custom_functions.html"
-		},
+          "en_US": "https://ekuiper.org/docs/en/latest/sqls/custom_functions.html",
+          "zh_CN": "https://ekuiper.org/docs/zh/latest/sqls/custom_functions.html"
+        },
 		"description": {
 			"en_US": "",
 			"zh_CN": ""
 		}
 	},
 	"name": "echo",
-	"functions": [
-		{
-			"name": "echo",
-			"example": "echo(col1)",
-			"hint": {
-				"en_US": "The parameter value is output as it is.",
-				"zh_CN": "原样输出参数值。"
-			},
-			"args": [
-				{
-					"name": "field",
-					"optional": false,
-					"control": "field",
-					"type": "string",
-					"hint": {
-						"en_US": "The field to echo.",
-						"zh_CN": "字段名"
-					},
-					"label": {
-						"en_US": "Field",
-						"zh_CN": "字段"
-					}
-				}
-			],
-			"return": {
+	"functions": [{
+		"name": "echo",
+		"example": "echo(col1)",
+		"hint": {
+			"en_US": "The parameter value is output as it is.",
+			"zh_CN": "原样输出参数值。"
+		},
+		"args": [
+			{
+				"name": "field",
+				"optional": false,
+				"control": "field",
 				"type": "string",
 				"hint": {
-					"en_US": "Echoed string",
-					"zh_CN": "回声值"
-				}
-			},
-			"node": {
-				"category": "function",
-				"icon": "iconPath",
+					"en_US": "The field to echo.",
+					"zh_CN": "字段名"
+				},
 				"label": {
-					"en_US": "Echo",
-					"zh_CN": "回声"
+					"en_US": "Field",
+					"zh_CN": "字段"
 				}
 			}
-		}]
-}
\ No newline at end of file
+		],
+		"return": {
+			"type": "string",
+			"hint": {
+				"en_US": "Echoed string",
+				"zh_CN": "回声值"
+			}
+		},
+		"node": {
+			"category": "function",
+			"icon": "iconPath",
+			"label": {
+				"en_US": "Echo",
+				"zh_CN": "回声"
+			}
+		}
+	}]
+}
diff --git a/sdk/go/example/mirror/sinks/file.json b/sdk/go/example/mirror/sinks/file.json
index 4bf4ce11..479b96cd 100644
--- a/sdk/go/example/mirror/sinks/file.json
+++ b/sdk/go/example/mirror/sinks/file.json
@@ -7,10 +7,6 @@
 			"company": "EMQ Technologies Co., Ltd",
 			"website": "https://www.emqx.io"
 		},
-		"helpUrl": {
-			"en_US": "https://github.com/lf-edge/ekuiper/blob/master/docs/en_US/plugins/sinks/file.md",
-			"zh_CN": "https://github.com/lf-edge/ekuiper/blob/master/docs/zh_CN/plugins/sinks/file.md"
-		},
 		"description": {
 			"en_US": "This a sink plugin for file, it can be used for saving the analysis data into file system.",
 			"zh_CN": "本插件为文件持久化插件，可以用于将分析数据存入指定的文件中。"
@@ -36,24 +32,23 @@
 		"name": "interval",
 		"default": 1000,
 		"optional": true,
-      "control": "text",
-      "type": "int",
-      "hint": {
-        "en_US": "The time interval (ms) for writing the analysis result.",
-        "zh_CN": "写入分析结果的时间间隔（毫秒）。"
-      },
-      "label": {
-        "en_US": "Intervals",
-        "zh_CN": "间隔时间"
-      }
-    }
-    ],
-  "node": {
-    "category": "Sink",
-    "icon": "iconPath",
-    "label": {
-      "en": "File",
-      "zh": "文件"
-    }
-  }
+		"control": "text",
+		"type": "int",
+		"hint": {
+			"en_US": "The time interval (ms) for writing the analysis result.",
+			"zh_CN": "写入分析结果的时间间隔（毫秒）。"
+		},
+		"label": {
+			"en_US": "Intervals",
+			"zh_CN": "间隔时间"
+		}
+	}],
+	"node": {
+		"category": "sink",
+		"icon": "iconPath",
+		"label": {
+			"en": "File",
+			"zh": "文件"
+		}
+	}
 }
diff --git a/sdk/go/example/mirror/sources/random.json b/sdk/go/example/mirror/sources/random.json
index ba9c8355..19fb8bd8 100644
--- a/sdk/go/example/mirror/sources/random.json
+++ b/sdk/go/example/mirror/sources/random.json
@@ -8,8 +8,8 @@
       "website": "https://www.emqx.io"
     },
     "helpUrl": {
-      "en_US": "https://github.com/lf-edge/ekuiper/blob/master/docs/en_US/plugins/sources/random.md",
-      "zh_CN": "https://github.com/lf-edge/ekuiper/blob/master/docs/zh_CN/plugins/sources/random.md"
+      "en_US": "https://ekuiper.org/docs/en/latest/guide/sources/plugin/random.html",
+      "zh_CN": "https://ekuiper.org/docs/zh/latest/guide/sources/plugin/random.html"
     },
     "description": {
       "en_US": "The source will generate random inputs with a specified pattern.",
diff --git a/sdk/python/ekuiper/runtime/connection.py b/sdk/python/ekuiper/runtime/connection.py
index 8bfe5f0f..194191c4 100644
--- a/sdk/python/ekuiper/runtime/connection.py
+++ b/sdk/python/ekuiper/runtime/connection.py
@@ -1,4 +1,4 @@
-#  Copyright 2021 EMQ Technologies Co., Ltd.
+#  Copyright 2021-2023 EMQ Technologies Co., Ltd.
 #
 #  Licensed under the Apache License, Version 2.0 (the "License");
 #  you may not use this file except in compliance with the License.
@@ -11,6 +11,7 @@
 #  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
+
 import logging
 import time
 from typing import Callable
@@ -27,10 +28,11 @@ class PairChannel:
             url = "ipc:///tmp/plugin_{}.ipc".format(name)
         else:
             url = "ipc:///tmp/func_{}.ipc".format(name)
+        logging.info("dialing {}".format(url))
         try:
             dial_with_retry(s, url)
         except Exception as e:
-            print(e)
+            logging.info("control/function channel {} cannot created {}".format(url, e))
             exit(0)
         self.sock = s
 
@@ -105,7 +107,8 @@ def dial_with_retry(sock, url: str):
         try:
             sock.dial(url, block=True)
             break
-        except Exception:
+        except Exception as e:
+            logging.debug("dial error {}".format(e))
             retry_count -= 1
             if retry_count < 0:
                 raise
diff --git a/sdk/python/ekuiper/runtime/plugin.py b/sdk/python/ekuiper/runtime/plugin.py
index 43f3497c..d994c132 100644
--- a/sdk/python/ekuiper/runtime/plugin.py
+++ b/sdk/python/ekuiper/runtime/plugin.py
@@ -1,4 +1,4 @@
-#  Copyright 2021 EMQ Technologies Co., Ltd.
+#  Copyright 2021-2023 EMQ Technologies Co., Ltd.
 #
 #  Licensed under the Apache License, Version 2.0 (the "License");
 #  you may not use this file except in compliance with the License.
@@ -11,6 +11,7 @@
 #  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
+
 import json
 import logging
 import sys
@@ -59,6 +60,7 @@ def start(c: PluginConfig):
     logging.info("starting plugin {}".format(c.name))
     ch = PairChannel(c.name, 0)
     ch.run(command_reply)
+    logging.info("started plugin {}".format(c.name))
 
 
 def init_vars(c: PluginConfig):
diff --git a/sdk/python/example/pysam/functions/revert.json b/sdk/python/example/pysam/functions/revert.json
index 23eeded7..99c2ec83 100644
--- a/sdk/python/example/pysam/functions/revert.json
+++ b/sdk/python/example/pysam/functions/revert.json
@@ -26,8 +26,8 @@
 				"control": "field",
 				"type": "string",
 				"hint": {
-					"en_US": "The field to revert.",
-					"zh_CN": "用于反转的字段名"
+					"en_US": "The field to revert",
+					"zh_CN": "字段名"
 				},
 				"label": {
 					"en_US": "Field",
@@ -39,15 +39,15 @@
 			"type": "string",
 			"hint": {
 				"en_US": "Reverted string",
-				"zh_CN": "反转值"
+				"zh_CN": "反转后的字符串"
 			}
 		},
 		"node": {
 			"category": "function",
 			"icon": "iconPath",
 			"label": {
-				"en_US": "Revert",
-				"zh_CN": "反转"
+				"en_US": "String Revert",
+				"zh_CN": "字符串反转"
 			}
 		}
 	}]
diff --git a/sdk/python/example/pysam/sinks/print.json b/sdk/python/example/pysam/sinks/print.json
index 03b5d5c4..2ed14288 100644
--- a/sdk/python/example/pysam/sinks/print.json
+++ b/sdk/python/example/pysam/sinks/print.json
@@ -1,24 +1,24 @@
 {
 	"about": {
 		"trial": true,
-      "author": {
-        "name": "EMQ",
-        "email": "contact@emqx.io",
-        "company": "EMQ Technologies Co., Ltd",
-        "website": "https://www.emqx.io"
-      },
-      "description": {
-        "en_US": "Example sink plugin to print with python logger",
-        "zh_CN": "示例 python 插件，使用 python 打出日志"
-      }
-    },
-  "properties": [],
-  "node": {
-    "category": "sink",
-    "icon": "iconPath",
-    "label": {
-      "en": "Print",
-      "zh": "Print"
-    }
-  }
+		"author": {
+			"name": "EMQ",
+			"email": "contact@emqx.io",
+			"company": "EMQ Technologies Co., Ltd",
+			"website": "https://www.emqx.io"
+		},
+		"description": {
+			"en_US": "Example sink plugin to print with python logger",
+			"zh_CN": "示例 python 插件，使用 python 打出日志"
+		}
+	},
+	"properties": [],
+	"node": {
+		"category": "sink",
+		"icon": "iconPath",
+		"label": {
+			"en": "Print",
+			"zh": "打印日志"
+		}
+	}
 }
diff --git a/sdk/python/example/pysam/sources/pyjson.json b/sdk/python/example/pysam/sources/pyjson.json
index 6a016860..21a35a1d 100644
--- a/sdk/python/example/pysam/sources/pyjson.json
+++ b/sdk/python/example/pysam/sources/pyjson.json
@@ -7,10 +7,6 @@
       "company": "EMQ Technologies Co., Ltd",
       "website": "https://www.emqx.io"
     },
-    "helpUrl": {
-      "en_US": "https://github.com/lf-edge/ekuiper/blob/master/docs/en_US/plugins/sources/random.md",
-      "zh_CN": "https://github.com/lf-edge/ekuiper/blob/master/docs/zh_CN/plugins/sources/random.md"
-    },
     "description": {
       "en_US": "The python source will send json data.",
       "zh_CN": "Python 源发送数据"
@@ -20,8 +16,8 @@
     "category": "source",
     "icon": "iconPath",
     "label": {
-      "en_US": "Pyjson",
-      "zh_CN": "Pyjson"
+      "en_US": "Python JSON Generator",
+      "zh_CN": "Python JSON 生成器"
     }
   }
 }


Test patch:
diff --git a/internal/plugin/portable/runtime/connection_test.go b/internal/plugin/portable/runtime/connection_test.go
index 582e5ab6..1fc7e7dd 100644
--- a/internal/plugin/portable/runtime/connection_test.go
+++ b/internal/plugin/portable/runtime/connection_test.go
@@ -17,6 +17,7 @@ package runtime
 import (
 	"fmt"
 	"github.com/lf-edge/ekuiper/internal/conf"
+	"github.com/lf-edge/ekuiper/internal/testx"
 	"github.com/lf-edge/ekuiper/internal/topo/context"
 	"github.com/lf-edge/ekuiper/internal/topo/state"
 	"github.com/lf-edge/ekuiper/pkg/api"
@@ -29,6 +30,10 @@ import (
 	"time"
 )
 
+func init() {
+	testx.InitEnv()
+}
+
 var okMsg = []byte("ok")
 
 func TestControlCh(t *testing.T) {
diff --git a/internal/server/rest_test.go b/internal/server/rest_test.go
index a26e62fe..18347615 100644
--- a/internal/server/rest_test.go
+++ b/internal/server/rest_test.go
@@ -258,6 +258,18 @@ func Test_rulesManageHandler(t *testing.T) {
 		t.Errorf("Expect\t%v\nBut got\t%v", 200, w1.Result().StatusCode)
 	}
 
+	//update wron rule
+	ruleJson = `{"id": "rule1","sql": "select * from alert1","actions": [{"nop": {}}]}`
+
+	buf2 = bytes.NewBuffer([]byte(ruleJson))
+	req1, _ = http.NewRequest(http.MethodPut, "http://localhost:8080/rules/rule1", buf2)
+	w1 = httptest.NewRecorder()
+	r.ServeHTTP(w1, req1)
+
+	if w1.Result().StatusCode != 400 {
+		t.Errorf("Expect\t%v\nBut got\t%v", 200, w1.Result().StatusCode)
+	}
+
 	//get rule
 	req1, _ = http.NewRequest(http.MethodGet, "http://localhost:8080/rules/rule1", bytes.NewBufferString("any"))
 	w1 = httptest.NewRecorder()
diff --git a/internal/topo/node/sink_node_test.go b/internal/topo/node/sink_node_test.go
index f1d0c45b..36c3365a 100644
--- a/internal/topo/node/sink_node_test.go
+++ b/internal/topo/node/sink_node_test.go
@@ -1,4 +1,4 @@
-// Copyright 2022 EMQ Technologies Co., Ltd.
+// Copyright 2022-2023 EMQ Technologies Co., Ltd.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -302,8 +302,15 @@ func TestFormat_Apply(t *testing.T) {
 		s := NewSinkNodeWithSink("mockSink", mockSink, tt.config)
 		s.Open(ctx, make(chan error))
 		s.input <- tt.data
-		time.Sleep(1 * time.Second)
-		results := mockSink.GetResults()
+		var results [][]byte
+		// try at most 5 seconds
+		for j := 0; j < 10; j++ {
+			time.Sleep(500 * time.Millisecond)
+			results = mockSink.GetResults()
+			if len(results) > 0 {
+				break
+			}
+		}
 		if !reflect.DeepEqual(tt.result, results) {
 			t.Errorf("%d \tresult mismatch:\n\nexp=%x\n\ngot=%x\n\n", i, tt.result, results)
 		}
diff --git a/internal/topo/operator/filter_test.go b/internal/topo/operator/filter_test.go
index f2db3e90..85df35f0 100644
--- a/internal/topo/operator/filter_test.go
+++ b/internal/topo/operator/filter_test.go
@@ -1,4 +1,4 @@
-// Copyright 2021-2022 EMQ Technologies Co., Ltd.
+// Copyright 2021-2023 EMQ Technologies Co., Ltd.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -374,7 +374,9 @@ func TestFilterPlan_Apply(t *testing.T) {
 					},
 				},
 			},
-			result: nil,
+			result: &xsql.WindowTuples{
+				Content: []xsql.TupleRow{},
+			},
 		},
 		{
 			sql: "SELECT id1 FROM src1 left join src2 on src1.id1 = src2.id2 WHERE src1.f1 = \"v1\" GROUP BY TUMBLINGWINDOW(ss, 10)",
@@ -481,7 +483,9 @@ func TestFilterPlan_Apply(t *testing.T) {
 					},
 				},
 			},
-			result: nil,
+			result: &xsql.JoinTuples{
+				Content: []*xsql.JoinTuple{},
+			},
 		},
 		{
 			sql: "SELECT abc FROM tbl WHERE meta(topic) = \"topic1\" ",
diff --git a/internal/topo/topotest/mocknode/mock_sink.go b/internal/topo/topotest/mocknode/mock_sink.go
index 83f4db3e..ea7f16e8 100644
--- a/internal/topo/topotest/mocknode/mock_sink.go
+++ b/internal/topo/topotest/mocknode/mock_sink.go
@@ -15,6 +15,7 @@
 package mocknode
 
 import (
+	"fmt"
 	"github.com/lf-edge/ekuiper/pkg/api"
 )
 
@@ -36,6 +37,7 @@ func (m *MockSink) Open(ctx api.StreamContext) error {
 
 func (m *MockSink) Collect(ctx api.StreamContext, item interface{}) error {
 	logger := ctx.GetLogger()
+	fmt.Println("mock sink receive ", item)
 	if v, _, err := ctx.TransformOutput(item); err == nil {
 		logger.Debugf("mock sink receive %s", item)
 		m.results = append(m.results, v)
diff --git a/internal/topo/topotest/window_rule_test.go b/internal/topo/topotest/window_rule_test.go
index 6de50104..691d2b1c 100644
--- a/internal/topo/topotest/window_rule_test.go
+++ b/internal/topo/topotest/window_rule_test.go
@@ -1,4 +1,4 @@
-// Copyright 2021-2022 EMQ Technologies Co., Ltd.
+// Copyright 2021-2023 EMQ Technologies Co., Ltd.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -757,22 +757,25 @@ func TestEventWindow(t *testing.T) {
 					"color":        "red",
 					"ts":           float64(1541152486013),
 				}},
+				{},
 				{{
 					"window_start": float64(1541152488000),
 					"window_end":   float64(1541152489000),
 					"color":        "yellow",
 					"ts":           float64(1541152488442),
 				}},
+				{},
+				{},
 			},
 			M: map[string]interface{}{
 				"op_4_project_0_exceptions_total":   int64(0),
 				"op_4_project_0_process_latency_us": int64(0),
-				"op_4_project_0_records_in_total":   int64(2),
-				"op_4_project_0_records_out_total":  int64(2),
+				"op_4_project_0_records_in_total":   int64(5),
+				"op_4_project_0_records_out_total":  int64(5),
 
 				"sink_mockSink_0_exceptions_total":  int64(0),
-				"sink_mockSink_0_records_in_total":  int64(2),
-				"sink_mockSink_0_records_out_total": int64(2),
+				"sink_mockSink_0_records_in_total":  int64(5),
+				"sink_mockSink_0_records_out_total": int64(5),
 
 				"source_demoE_0_exceptions_total":  int64(0),
 				"source_demoE_0_records_in_total":  int64(6),
@@ -786,7 +789,7 @@ func TestEventWindow(t *testing.T) {
 				"op_3_filter_0_exceptions_total":   int64(0),
 				"op_3_filter_0_process_latency_us": int64(0),
 				"op_3_filter_0_records_in_total":   int64(5),
-				"op_3_filter_0_records_out_total":  int64(2),
+				"op_3_filter_0_records_out_total":  int64(5),
 			},
 		}, {
 			Name: `TestEventWindowRule3`,
diff --git a/test/graph_mix_rule.jmx b/test/graph_mix_rule.jmx
index e8b896ec..490e1083 100644
--- a/test/graph_mix_rule.jmx
+++ b/test/graph_mix_rule.jmx
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!--
-  ~ Copyright 2022 EMQ Technologies Co., Ltd.
+  ~ Copyright 2023 EMQ Technologies Co., Ltd.
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
   ~ you may not use this file except in compliance with the License.
@@ -179,7 +179,7 @@
         &quot;type&quot;: &quot;operator&quot;,&#xd;
         &quot;nodeType&quot;: &quot;filter&quot;,&#xd;
         &quot;props&quot;: {&#xd;
-          &quot;expr&quot;: &quot;humidity &gt; 30&quot;&#xd;
+          &quot;expr&quot;: &quot;humidity &gt; 20&quot;&#xd;
         }&#xd;
       },&#xd;
       &quot;aggfunc&quot;: {&#xd;
@@ -356,7 +356,7 @@
             <hashTree/>
             <JSONPathAssertion guiclass="JSONPathAssertionGui" testclass="JSONPathAssertion" testname="JSON Assertion" enabled="true">
               <stringProp name="JSON_PATH">$.sink_mqtt2_0_records_out_total</stringProp>
-              <stringProp name="EXPECTED_VALUE">4</stringProp>
+              <stringProp name="EXPECTED_VALUE">5</stringProp>
               <boolProp name="JSONVALIDATION">true</boolProp>
               <boolProp name="EXPECT_NULL">false</boolProp>
               <boolProp name="INVERT">false</boolProp>
@@ -521,7 +521,7 @@
         <stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
         <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
           <boolProp name="LoopController.continue_forever">false</boolProp>
-          <stringProp name="LoopController.loops">4</stringProp>
+          <stringProp name="LoopController.loops">5</stringProp>
         </elementProp>
         <stringProp name="ThreadGroup.num_threads">1</stringProp>
         <stringProp name="ThreadGroup.ramp_time">1</stringProp>
diff --git a/test/graph_mix_rule_result2.txt b/test/graph_mix_rule_result2.txt
index 95eaa140..0a2bf5bc 100644
--- a/test/graph_mix_rule_result2.txt
+++ b/test/graph_mix_rule_result2.txt
@@ -1,4 +1,5 @@
-27.2,0.8792730616507244
+22.5,-0.4871745124605095
+24.85,-0.4871745124605095
 27.15,0.8792730616507244
 26.950000000000003,0.9224328169230859
 26.85,0.9953511049115592
\ No newline at end of file
diff --git a/test/portable_end_2_end.jmx b/test/portable_end_2_end.jmx
index 75cde57a..5efed493 100644
--- a/test/portable_end_2_end.jmx
+++ b/test/portable_end_2_end.jmx
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!--
-  ~ Copyright 2021-2022 EMQ Technologies Co., Ltd.
+  ~ Copyright 2023 EMQ Technologies Co., Ltd.
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
   ~ you may not use this file except in compliance with the License.
@@ -527,7 +527,7 @@ if (count&lt;=0)
                   <boolProp name="HTTPArgument.always_encode">false</boolProp>
                   <stringProp name="Argument.value">{&#xd;
   &quot;id&quot;: &quot;ruleExtP&quot;,&#xd;
-  &quot;sql&quot;: &quot;SELECT * FROM ext2&quot;,&#xd;
+  &quot;sql&quot;: &quot;SELECT revert(name) FROM ext2&quot;,&#xd;
   &quot;actions&quot;: [{&#xd;
     &quot;print&quot;: {&#xd;
     }&#xd;
@@ -586,27 +586,23 @@ if (count&lt;=0)
             <stringProp name="HTTPSampler.response_timeout"></stringProp>
           </HTTPSamplerProxy>
           <hashTree>
-            <JSONPostProcessor guiclass="JSONPostProcessorGui" testclass="JSONPostProcessor" testname="JSON Extractor" enabled="true">
-              <stringProp name="JSONPostProcessor.referenceNames">fileout</stringProp>
-              <stringProp name="JSONPostProcessor.jsonPathExprs">$.sink_print_0_0_records_out_total</stringProp>
-              <stringProp name="JSONPostProcessor.match_numbers"></stringProp>
-            </JSONPostProcessor>
+            <JSONPathAssertion guiclass="JSONPathAssertionGui" testclass="JSONPathAssertion" testname="JSON Assertion" enabled="true">
+              <stringProp name="JSON_PATH">$.op_2_project_0_records_out_total</stringProp>
+              <stringProp name="EXPECTED_VALUE">0</stringProp>
+              <boolProp name="JSONVALIDATION">true</boolProp>
+              <boolProp name="EXPECT_NULL">false</boolProp>
+              <boolProp name="INVERT">true</boolProp>
+              <boolProp name="ISREGEX">true</boolProp>
+            </JSONPathAssertion>
             <hashTree/>
-            <JSR223Assertion guiclass="TestBeanGUI" testclass="JSR223Assertion" testname="JSR223 Assertion" enabled="true">
-              <stringProp name="cacheKey">true</stringProp>
-              <stringProp name="filename"></stringProp>
-              <stringProp name="parameters"></stringProp>
-              <stringProp name="script">String jsonString =  vars.get(&quot;fileout&quot;);
-int count = jsonString.toInteger()
-log.info (&quot;The file out metric &quot; + count); 
-
-if (count&lt;=0) 
-  {
-   AssertionResult.setFailureMessage(&quot;The sink output is 0&quot;);
-   AssertionResult.setFailure(true); 
-  }</stringProp>
-              <stringProp name="scriptLanguage">groovy</stringProp>
-            </JSR223Assertion>
+            <JSONPathAssertion guiclass="JSONPathAssertionGui" testclass="JSONPathAssertion" testname="JSON Assertion" enabled="true">
+              <stringProp name="JSON_PATH">$.op_2_project_0_exceptions_total</stringProp>
+              <stringProp name="EXPECTED_VALUE">0</stringProp>
+              <boolProp name="JSONVALIDATION">true</boolProp>
+              <boolProp name="EXPECT_NULL">false</boolProp>
+              <boolProp name="INVERT">false</boolProp>
+              <boolProp name="ISREGEX">true</boolProp>
+            </JSONPathAssertion>
             <hashTree/>
           </hashTree>
           <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="API_UpdatePlugin" enabled="true">
@@ -672,27 +668,23 @@ if (count&lt;=0)
             <stringProp name="HTTPSampler.response_timeout"></stringProp>
           </HTTPSamplerProxy>
           <hashTree>
-            <JSONPostProcessor guiclass="JSONPostProcessorGui" testclass="JSONPostProcessor" testname="JSON Extractor" enabled="true">
-              <stringProp name="JSONPostProcessor.referenceNames">fileout</stringProp>
-              <stringProp name="JSONPostProcessor.jsonPathExprs">$.sink_print_0_0_records_out_total</stringProp>
-              <stringProp name="JSONPostProcessor.match_numbers"></stringProp>
-            </JSONPostProcessor>
+            <JSONPathAssertion guiclass="JSONPathAssertionGui" testclass="JSONPathAssertion" testname="JSON Assertion" enabled="true">
+              <stringProp name="JSON_PATH">$.op_2_project_0_records_out_total</stringProp>
+              <stringProp name="EXPECTED_VALUE">0</stringProp>
+              <boolProp name="JSONVALIDATION">true</boolProp>
+              <boolProp name="EXPECT_NULL">false</boolProp>
+              <boolProp name="INVERT">true</boolProp>
+              <boolProp name="ISREGEX">true</boolProp>
+            </JSONPathAssertion>
             <hashTree/>
-            <JSR223Assertion guiclass="TestBeanGUI" testclass="JSR223Assertion" testname="JSR223 Assertion" enabled="true">
-              <stringProp name="cacheKey">true</stringProp>
-              <stringProp name="filename"></stringProp>
-              <stringProp name="parameters"></stringProp>
-              <stringProp name="script">String jsonString =  vars.get(&quot;fileout&quot;);
-int count = jsonString.toInteger()
-log.info (&quot;The file out metric &quot; + count); 
-
-if (count&lt;=0) 
-  {
-   AssertionResult.setFailureMessage(&quot;The sink output is 0&quot;);
-   AssertionResult.setFailure(true); 
-  }</stringProp>
-              <stringProp name="scriptLanguage">groovy</stringProp>
-            </JSR223Assertion>
+            <JSONPathAssertion guiclass="JSONPathAssertionGui" testclass="JSONPathAssertion" testname="JSON Assertion" enabled="true">
+              <stringProp name="JSON_PATH">$.op_2_project_0_exceptions_total</stringProp>
+              <stringProp name="EXPECTED_VALUE">0</stringProp>
+              <boolProp name="JSONVALIDATION">true</boolProp>
+              <boolProp name="EXPECT_NULL">false</boolProp>
+              <boolProp name="INVERT">false</boolProp>
+              <boolProp name="ISREGEX">true</boolProp>
+            </JSONPathAssertion>
             <hashTree/>
           </hashTree>
           <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="API_DropRule" enabled="true">
@@ -791,70 +783,6 @@ if (count&lt;=0)
           <hashTree/>
         </hashTree>
       </hashTree>
-      <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Result" enabled="false">
-        <stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
-        <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
-          <boolProp name="LoopController.continue_forever">false</boolProp>
-          <stringProp name="LoopController.loops">1</stringProp>
-        </elementProp>
-        <stringProp name="ThreadGroup.num_threads">1</stringProp>
-        <stringProp name="ThreadGroup.ramp_time">0</stringProp>
-        <boolProp name="ThreadGroup.scheduler">false</boolProp>
-        <stringProp name="ThreadGroup.duration"></stringProp>
-        <stringProp name="ThreadGroup.delay"></stringProp>
-        <boolProp name="ThreadGroup.same_user_on_next_iteration">true</boolProp>
-      </ThreadGroup>
-      <hashTree>
-        <ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="View Results Tree" enabled="true">
-          <boolProp name="ResultCollector.error_logging">false</boolProp>
-          <objProp>
-            <name>saveConfig</name>
-            <value class="SampleSaveConfiguration">
-              <time>true</time>
-              <latency>true</latency>
-              <timestamp>true</timestamp>
-              <success>true</success>
-              <label>true</label>
-              <code>true</code>
-              <message>true</message>
-              <threadName>true</threadName>
-              <dataType>true</dataType>
-              <encoding>false</encoding>
-              <assertions>true</assertions>
-              <subresults>true</subresults>
-              <responseData>false</responseData>
-              <samplerData>false</samplerData>
-              <xml>false</xml>
-              <fieldNames>true</fieldNames>
-              <responseHeaders>false</responseHeaders>
-              <requestHeaders>false</requestHeaders>
-              <responseDataOnError>false</responseDataOnError>
-              <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
-              <assertionsResultsToSave>0</assertionsResultsToSave>
-              <bytes>true</bytes>
-              <sentBytes>true</sentBytes>
-              <threadCounts>true</threadCounts>
-              <idleTime>true</idleTime>
-              <connectTime>true</connectTime>
-            </value>
-          </objProp>
-          <stringProp name="filename"></stringProp>
-        </ResultCollector>
-        <hashTree/>
-        <SystemSampler guiclass="SystemSamplerGui" testclass="SystemSampler" testname="App_Service" enabled="true">
-          <boolProp name="SystemSampler.checkReturnCode">false</boolProp>
-          <stringProp name="SystemSampler.expectedReturnCode">0</stringProp>
-          <stringProp name="SystemSampler.command">./server</stringProp>
-          <elementProp name="SystemSampler.arguments" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
-            <collectionProp name="Arguments.arguments"/>
-          </elementProp>
-          <elementProp name="SystemSampler.environment" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
-            <collectionProp name="Arguments.arguments"/>
-          </elementProp>
-          <stringProp name="SystemSampler.directory">${__property(fvt,,)}/test/plugins/service/</stringProp>
-        </SystemSampler>
-        <hashTree/>
-      </hashTree>
     </hashTree>
   </hashTree>
 </jmeterTestPlan>