diff --git a/metrics/grafana/client_java_summary.json b/metrics/grafana/client_java_summary.json index 46895dd20aa..48c4be772c6 100644 --- a/metrics/grafana/client_java_summary.json +++ b/metrics/grafana/client_java_summary.json @@ -3008,2111 +3008,6 @@ ], "title": "Circuit Breaker", "type": "row" - }, - { - "collapsed": true, - "datasource": null, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 21 - }, - "id": 96, - "panels": [ - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_TEST-CLUSTER}", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 14 - }, - "hiddenSeries": false, - "id": 97, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "7.5.7", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "exemplar": true, - "expr": "histogram_quantile(1, sum(rate(netty_nio_socket_channel_read_duration_seconds_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le))", - "hide": false, - "interval": "", - "legendFormat": "read max", - "queryType": "randomWalk", - "refId": "A" - }, - { - "exemplar": true, - "expr": "histogram_quantile(1, sum(rate(netty_nio_socket_channel_write_duration_seconds_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le))", - "hide": false, - "interval": "", - "legendFormat": "write max", - "queryType": "randomWalk", - "refId": "B" - }, - { - "exemplar": true, - "expr": "histogram_quantile(0.99, sum(rate(netty_nio_socket_channel_read_duration_seconds_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le))", - "hide": false, - "interval": "", - "legendFormat": "read P99", - "queryType": "randomWalk", - "refId": "C" - }, - { - "exemplar": true, - "expr": "histogram_quantile(0.99, sum(rate(netty_nio_socket_channel_write_duration_seconds_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le))", - "hide": false, - "interval": "", - "legendFormat": "write P99", - "queryType": "randomWalk", - "refId": "D" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Socket Duration", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:764", - "format": "s", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "$$hashKey": "object:765", - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_TEST-CLUSTER}", - "description": "", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 14 - }, - "hiddenSeries": false, - "id": 98, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "7.5.7", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "exemplar": true, - "expr": "histogram_quantile(1, sum(rate(netty_nio_socket_channel_read_bytes_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le))", - "hide": false, - "interval": "", - "legendFormat": "read bytes max", - "queryType": "randomWalk", - "refId": "A" - }, - { - "exemplar": true, - "expr": "histogram_quantile(1, sum(rate(netty_nio_socket_channel_write_bytes_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le))", - "hide": false, - "interval": "", - "legendFormat": "write bytes max", - "queryType": "randomWalk", - "refId": "B" - }, - { - "exemplar": true, - "expr": "histogram_quantile(1, sum(rate(netty_nio_socket_channel_read_left_bytes_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le))", - "hide": true, - "interval": "", - "legendFormat": "read left bytes max", - "queryType": "randomWalk", - "refId": "C" - }, - { - "exemplar": true, - "expr": "histogram_quantile(1, sum(rate(netty_nio_socket_channel_write_left_bytes_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le))", - "hide": false, - "interval": "", - "legendFormat": "write left bytes max", - "queryType": "randomWalk", - "refId": "D" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Socket bytes", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:764", - "format": "decbytes", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "$$hashKey": "object:765", - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - } - ], - "title": "Socket Metrics", - "type": "row" - }, - { - "collapsed": true, - "datasource": null, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 22 - }, - "id": 94, - "panels": [ - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_TEST-CLUSTER}", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 23 - }, - "hiddenSeries": false, - "id": 76, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "7.5.7", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "exemplar": true, - "expr": "histogram_quantile(1, sum(rate(grpc_netty_write_queue_channel_flush_duration_seconds_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le))", - "interval": "", - "legendFormat": "max", - "queryType": "randomWalk", - "refId": "A" - }, - { - "exemplar": true, - "expr": "histogram_quantile(0.99, sum(rate(grpc_netty_write_queue_channel_flush_duration_seconds_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le))", - "hide": false, - "interval": "", - "legendFormat": "p99", - "queryType": "randomWalk", - "refId": "B" - }, - { - "exemplar": true, - "expr": "histogram_quantile(0.5, sum(rate(grpc_netty_write_queue_channel_flush_duration_seconds_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le))", - "hide": false, - "interval": "", - "legendFormat": "p50", - "queryType": "randomWalk", - "refId": "C" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "WriteQueue Channel Flush Duration", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:764", - "format": "s", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "$$hashKey": "object:765", - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_TEST-CLUSTER}", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 23 - }, - "hiddenSeries": false, - "id": 86, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "7.5.7", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "exemplar": true, - "expr": "histogram_quantile(1, sum(rate(netty_http2_flush_flow_control_write_duration_seconds_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le))", - "interval": "", - "legendFormat": "max", - "queryType": "randomWalk", - "refId": "A" - }, - { - "exemplar": true, - "expr": "histogram_quantile(0.99, sum(rate(netty_http2_flush_flow_control_write_duration_seconds_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le))", - "hide": false, - "interval": "", - "legendFormat": "P99", - "queryType": "randomWalk", - "refId": "B" - }, - { - "exemplar": true, - "expr": "histogram_quantile(0.5, sum(rate(netty_http2_flush_flow_control_write_duration_seconds_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le))", - "hide": false, - "interval": "", - "legendFormat": "P50", - "queryType": "randomWalk", - "refId": "C" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "H2 Flush Write Flow Controll Duration", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:547", - "format": "s", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "$$hashKey": "object:548", - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_TEST-CLUSTER}", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 31 - }, - "hiddenSeries": false, - "id": 78, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "7.5.7", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "exemplar": true, - "expr": "histogram_quantile(1, sum(rate(netty_http2_flush_ctx_flush_duration_seconds_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le))", - "interval": "", - "legendFormat": "max", - "queryType": "randomWalk", - "refId": "A" - }, - { - "exemplar": true, - "expr": "histogram_quantile(0.99, sum(rate(netty_http2_flush_ctx_flush_duration_seconds_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le))", - "hide": false, - "interval": "", - "legendFormat": "P99", - "queryType": "randomWalk", - "refId": "B" - }, - { - "exemplar": true, - "expr": "histogram_quantile(0.5, sum(rate(netty_http2_flush_ctx_flush_duration_seconds_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le))", - "hide": false, - "interval": "", - "legendFormat": "P50", - "queryType": "randomWalk", - "refId": "C" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Netty HTTP2 Context Flush Duration", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:108", - "format": "s", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "$$hashKey": "object:109", - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_TEST-CLUSTER}", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 31 - }, - "hiddenSeries": false, - "id": 88, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "7.5.7", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "exemplar": true, - "expr": "histogram_quantile(1, sum(rate(netty_http2_byte_distributed_duration_seconds_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le))", - "interval": "", - "legendFormat": "max", - "queryType": "randomWalk", - "refId": "A" - }, - { - "exemplar": true, - "expr": "histogram_quantile(0.99, sum(rate(netty_http2_byte_distributed_duration_seconds_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le))", - "hide": false, - "interval": "", - "legendFormat": "P99", - "queryType": "randomWalk", - "refId": "B" - }, - { - "exemplar": true, - "expr": "histogram_quantile(0.50, sum(rate(netty_http2_byte_distributed_duration_seconds_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le))", - "hide": false, - "interval": "", - "legendFormat": "P50", - "queryType": "randomWalk", - "refId": "C" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "H2 Distribute Bytes Duration", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:636", - "format": "s", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "$$hashKey": "object:637", - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - } - ], - "title": "Channel Flush", - "type": "row" - }, - { - "collapsed": true, - "datasource": null, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 23 - }, - "id": 92, - "panels": [ - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_TEST-CLUSTER}", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 40 - }, - "hiddenSeries": false, - "id": 82, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "7.5.7", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "exemplar": true, - "expr": "histogram_quantile(1, sum(rate(grpc_netty_client_stream_create_future_duration_seconds_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le))", - "hide": false, - "interval": "", - "legendFormat": "max", - "refId": "A" - }, - { - "exemplar": true, - "expr": "histogram_quantile(0.99, sum(rate(grpc_netty_client_stream_create_future_duration_seconds_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le))", - "interval": "", - "legendFormat": "P99", - "queryType": "randomWalk", - "refId": "B" - }, - { - "exemplar": true, - "expr": "histogram_quantile(0.50, sum(rate(grpc_netty_client_stream_create_future_duration_seconds_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le))", - "hide": false, - "interval": "", - "legendFormat": "P50", - "queryType": "randomWalk", - "refId": "C" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Client Stream CreateFuture Duration", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:286", - "format": "s", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "$$hashKey": "object:287", - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_TEST-CLUSTER}", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 40 - }, - "hiddenSeries": false, - "id": 84, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "7.5.7", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "exemplar": true, - "expr": "histogram_quantile(1, sum(rate(grpc_netty_client_stream_write_header_duration_seconds_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le))", - "interval": "", - "legendFormat": "max", - "queryType": "randomWalk", - "refId": "A" - }, - { - "exemplar": true, - "expr": "histogram_quantile(0.99, sum(rate(grpc_netty_client_stream_write_header_duration_seconds_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le))", - "hide": false, - "interval": "", - "legendFormat": "P99", - "queryType": "randomWalk", - "refId": "B" - }, - { - "exemplar": true, - "expr": "histogram_quantile(0.50, sum(rate(grpc_netty_client_stream_write_header_duration_seconds_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le))", - "hide": false, - "interval": "", - "legendFormat": "P50", - "queryType": "randomWalk", - "refId": "C" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Client Stream WriteHeader Duration", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:375", - "format": "s", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "$$hashKey": "object:376", - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_TEST-CLUSTER}", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 48 - }, - "hiddenSeries": false, - "id": 80, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "7.5.7", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "exemplar": true, - "expr": "histogram_quantile(1, sum(rate(grpc_netty_client_stream_add_listener_duration_seconds_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le))", - "interval": "", - "legendFormat": "max", - "queryType": "randomWalk", - "refId": "A" - }, - { - "exemplar": true, - "expr": "histogram_quantile(0.99, sum(rate(grpc_netty_client_stream_add_listener_duration_seconds_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le))", - "hide": false, - "interval": "", - "legendFormat": "P99", - "queryType": "randomWalk", - "refId": "B" - }, - { - "exemplar": true, - "expr": "histogram_quantile(0.50, sum(rate(grpc_netty_client_stream_add_listener_duration_seconds_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le))", - "hide": false, - "interval": "", - "legendFormat": "P50", - "queryType": "randomWalk", - "refId": "C" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Client Stream AddListener", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:197", - "format": "s", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "$$hashKey": "object:198", - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_TEST-CLUSTER}", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 48 - }, - "hiddenSeries": false, - "id": 90, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "7.5.7", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "exemplar": true, - "expr": "histogram_quantile(1, sum(rate(netty_http2_frame_writer_write_header_duration_seconds_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le))", - "interval": "", - "legendFormat": "max", - "queryType": "randomWalk", - "refId": "A" - }, - { - "exemplar": true, - "expr": "histogram_quantile(0.99, sum(rate(netty_http2_frame_writer_write_header_duration_seconds_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le))", - "hide": false, - "interval": "", - "legendFormat": "P99", - "queryType": "randomWalk", - "refId": "B" - }, - { - "exemplar": true, - "expr": "histogram_quantile(0.50, sum(rate(netty_http2_frame_writer_write_header_duration_seconds_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le))", - "hide": false, - "interval": "", - "legendFormat": "P50", - "queryType": "randomWalk", - "refId": "C" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": " Netty Client Stream Encode Header Duration", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:156", - "format": "s", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "$$hashKey": "object:157", - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - } - ], - "title": "CreateStream", - "type": "row" - }, - { - "collapsed": true, - "datasource": null, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 24 - }, - "id": 64, - "panels": [ - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_TEST-CLUSTER}", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 57 - }, - "hiddenSeries": false, - "id": 70, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "7.5.7", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": true, - "steppedLine": false, - "targets": [ - { - "exemplar": true, - "expr": "histogram_quantile(1, sum(rate(grpc_client_async_unary_request_call_duration_seconds_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le, phase))", - "interval": "", - "legendFormat": "{{ phase }} max", - "queryType": "randomWalk", - "refId": "A" - }, - { - "exemplar": true, - "expr": "histogram_quantile(1, sum(rate(grpc_client_blocking_unary_request_wait_duration_seconds_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le))", - "hide": false, - "interval": "", - "legendFormat": "wait on future max", - "queryType": "randomWalk", - "refId": "B" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Start Call Duration", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:472", - "format": "s", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "$$hashKey": "object:473", - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_TEST-CLUSTER}", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 57 - }, - "hiddenSeries": false, - "id": 72, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "7.5.7", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "exemplar": true, - "expr": "histogram_quantile(1, sum(rate(grpc_netty_write_queue_flush_duration_seconds_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le))", - "interval": "", - "legendFormat": "max", - "queryType": "randomWalk", - "refId": "A" - }, - { - "exemplar": true, - "expr": "histogram_quantile(0.99, sum(rate(grpc_netty_write_queue_flush_duration_seconds_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le))", - "hide": false, - "interval": "", - "legendFormat": "p99", - "queryType": "randomWalk", - "refId": "B" - }, - { - "exemplar": true, - "expr": "histogram_quantile(0.5, sum(rate(grpc_netty_write_queue_flush_duration_seconds_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le))", - "hide": false, - "interval": "", - "legendFormat": "p50", - "queryType": "randomWalk", - "refId": "C" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "WriteQueue Flush Duration", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:552", - "format": "s", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "$$hashKey": "object:553", - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_TEST-CLUSTER}", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 65 - }, - "hiddenSeries": false, - "id": 75, - "legend": { - "alignAsTable": true, - "avg": false, - "current": false, - "max": true, - "min": false, - "rightSide": true, - "show": true, - "sort": "max", - "sortDesc": true, - "total": false, - "values": true - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "7.5.7", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "exemplar": true, - "expr": "histogram_quantile(1, sum(rate(grpc_netty_write_queue_cmd_run_duration_seconds_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le, type))", - "interval": "", - "legendFormat": "{{type}} max", - "queryType": "randomWalk", - "refId": "A" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "WriteQueue Cmd Run Duration", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:764", - "format": "s", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "$$hashKey": "object:765", - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_TEST-CLUSTER}", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 65 - }, - "hiddenSeries": false, - "id": 66, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 2, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "7.5.7", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "exemplar": true, - "expr": "histogram_quantile(1, sum(rate(grpc_netty_write_queue_pending_duration_ms_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le))", - "interval": "", - "legendFormat": "max", - "queryType": "randomWalk", - "refId": "A" - }, - { - "exemplar": true, - "expr": "histogram_quantile(0.99, sum(rate(grpc_netty_write_queue_pending_duration_ms_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le))", - "hide": false, - "interval": "", - "legendFormat": "p99", - "queryType": "randomWalk", - "refId": "B" - }, - { - "exemplar": true, - "expr": "histogram_quantile(0.5, sum(rate(grpc_netty_write_queue_pending_duration_ms_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le))", - "hide": false, - "interval": "", - "legendFormat": "p50", - "queryType": "randomWalk", - "refId": "C" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "WriteQueue CMD Pending Duration", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:173", - "format": "ms", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "$$hashKey": "object:174", - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_TEST-CLUSTER}", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 73 - }, - "hiddenSeries": false, - "id": 74, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "7.5.7", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "exemplar": true, - "expr": "histogram_quantile(1, sum(rate(grpc_netty_write_queue_batch_size_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le))", - "interval": "", - "legendFormat": "max", - "queryType": "randomWalk", - "refId": "A" - }, - { - "exemplar": true, - "expr": "histogram_quantile(0.99, sum(rate(grpc_netty_write_queue_batch_size_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le))", - "hide": false, - "interval": "", - "legendFormat": "p99", - "queryType": "randomWalk", - "refId": "B" - }, - { - "exemplar": true, - "expr": "histogram_quantile(0.5, sum(rate(grpc_netty_write_queue_batch_size_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le))", - "hide": false, - "interval": "", - "legendFormat": "p50", - "queryType": "randomWalk", - "refId": "C" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "WriteQueue Batch Size", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:764", - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "$$hashKey": "object:765", - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_TEST-CLUSTER}", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 73 - }, - "hiddenSeries": false, - "id": 68, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "7.5.7", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "exemplar": true, - "expr": "histogram_quantile(1, sum(rate(grpc_netty_write_queue_wait_batch_duration_seconds_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le))", - "hide": false, - "interval": "", - "legendFormat": "max", - "queryType": "randomWalk", - "refId": "A" - }, - { - "exemplar": true, - "expr": "histogram_quantile(0.99, sum(rate(grpc_netty_write_queue_wait_batch_duration_seconds_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le))", - "hide": false, - "interval": "", - "legendFormat": "p99", - "refId": "B" - }, - { - "exemplar": true, - "expr": "histogram_quantile(0.5, sum(rate(grpc_netty_write_queue_wait_batch_duration_seconds_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le))", - "hide": false, - "interval": "", - "legendFormat": "p50", - "refId": "C" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "WriteQueue Wait Batch Duration", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:243", - "format": "s", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "$$hashKey": "object:244", - "format": "s", - "label": "", - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_TEST-CLUSTER}", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 33 - }, - "hiddenSeries": false, - "id": 105, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "7.5.7", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "exemplar": true, - "expr": "histogram_quantile(1, sum(rate(grpc_threadless_executor_latency_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le, phase))", - "interval": "", - "legendFormat": "{{ phase }} max", - "queryType": "randomWalk", - "refId": "A" - }, - { - "exemplar": true, - "expr": "histogram_quantile(0.99, sum(rate(grpc_threadless_executor_latency_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le, phase))", - "hide": false, - "interval": "", - "legendFormat": "{{ phase }} p99", - "refId": "B" - } - ], - "thresholds": [], - "timeRegions": [], - "title": "ThreadlessExecutor Lock", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - } - ], - "title": "gRPC internal", - "type": "row" - }, - { - "collapsed": true, - "datasource": null, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 25 - }, - "id": 100, - "panels": [ - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_TEST-CLUSTER}", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 82 - }, - "hiddenSeries": false, - "id": 101, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "7.5.7", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": true, - "steppedLine": false, - "targets": [ - { - "exemplar": true, - "expr": "histogram_quantile(1, sum(rate(perfmark_client_call_impl_duration_seconds_bucket{instance=~\"$instance\"}[$__rate_interval])) by (le, type))", - "interval": "", - "legendFormat": "{{type}} max", - "queryType": "randomWalk", - "refId": "A" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Client Call Implementation", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:472", - "format": "s", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "$$hashKey": "object:473", - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - } - ], - "title": "PerfMark", - "type": "row" } ], "refresh": "5s", diff --git a/src/main/java/io/grpc/internal/ClientCallImpl.java b/src/main/java/io/grpc/internal/ClientCallImpl.java deleted file mode 100644 index 75b769d6f80..00000000000 --- a/src/main/java/io/grpc/internal/ClientCallImpl.java +++ /dev/null @@ -1,854 +0,0 @@ -/* - * Copyright 2014 The gRPC Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * 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. - */ - -package io.grpc.internal; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; -import static com.google.common.util.concurrent.MoreExecutors.directExecutor; -import static io.grpc.Contexts.statusFromCancelled; -import static io.grpc.Status.DEADLINE_EXCEEDED; -import static io.grpc.internal.GrpcUtil.CONTENT_ACCEPT_ENCODING_KEY; -import static io.grpc.internal.GrpcUtil.CONTENT_ENCODING_KEY; -import static io.grpc.internal.GrpcUtil.CONTENT_LENGTH_KEY; -import static io.grpc.internal.GrpcUtil.MESSAGE_ACCEPT_ENCODING_KEY; -import static io.grpc.internal.GrpcUtil.MESSAGE_ENCODING_KEY; -import static java.lang.Math.max; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.MoreObjects; -import io.grpc.Attributes; -import io.grpc.CallOptions; -import io.grpc.ClientCall; -import io.grpc.ClientStreamTracer; -import io.grpc.Codec; -import io.grpc.Compressor; -import io.grpc.CompressorRegistry; -import io.grpc.Context; -import io.grpc.Context.CancellationListener; -import io.grpc.Deadline; -import io.grpc.DecompressorRegistry; -import io.grpc.InternalConfigSelector; -import io.grpc.InternalDecompressorRegistry; -import io.grpc.Metadata; -import io.grpc.MethodDescriptor; -import io.grpc.MethodDescriptor.MethodType; -import io.grpc.Status; -import io.grpc.internal.ManagedChannelServiceConfig.MethodInfo; -import io.perfmark.Link; -import io.perfmark.PerfMark; -import io.perfmark.Tag; -import io.prometheus.client.Histogram; -import java.io.InputStream; -import java.nio.charset.Charset; -import java.util.Locale; -import java.util.concurrent.CancellationException; -import java.util.concurrent.Executor; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import java.util.logging.Logger; -import javax.annotation.Nullable; -import org.tikv.common.util.HistogramUtils; - -/** Implementation of {@link ClientCall}. */ -final class ClientCallImpl extends ClientCall { - - public static final Histogram perfmarkClientCallImplDuration = - HistogramUtils.buildDuration() - .name("perfmark_client_call_impl_duration_seconds") - .help("Perfmark client call impl duration seconds") - .labelNames("type") - .register(); - - private static final Logger log = Logger.getLogger(ClientCallImpl.class.getName()); - private static final byte[] FULL_STREAM_DECOMPRESSION_ENCODINGS = - "gzip".getBytes(Charset.forName("US-ASCII")); - - private final MethodDescriptor method; - private final Tag tag; - private final Executor callExecutor; - private final boolean callExecutorIsDirect; - private final CallTracer channelCallsTracer; - private final Context context; - private volatile ScheduledFuture deadlineCancellationFuture; - private final boolean unaryRequest; - private CallOptions callOptions; - private ClientStream stream; - private volatile boolean cancelListenersShouldBeRemoved; - private boolean cancelCalled; - private boolean halfCloseCalled; - private final ClientStreamProvider clientStreamProvider; - private final ContextCancellationListener cancellationListener = - new ContextCancellationListener(); - private final ScheduledExecutorService deadlineCancellationExecutor; - private boolean fullStreamDecompression; - private DecompressorRegistry decompressorRegistry = DecompressorRegistry.getDefaultInstance(); - private CompressorRegistry compressorRegistry = CompressorRegistry.getDefaultInstance(); - - ClientCallImpl( - MethodDescriptor method, - Executor executor, - CallOptions callOptions, - ClientStreamProvider clientStreamProvider, - ScheduledExecutorService deadlineCancellationExecutor, - CallTracer channelCallsTracer, - // TODO(zdapeng): remove this arg - @Nullable InternalConfigSelector configSelector) { - this.method = method; - // TODO(carl-mastrangelo): consider moving this construction to ManagedChannelImpl. - this.tag = PerfMark.createTag(method.getFullMethodName(), System.identityHashCode(this)); - // If we know that the executor is a direct executor, we don't need to wrap it with a - // SerializingExecutor. This is purely for performance reasons. - // See https://github.com/grpc/grpc-java/issues/368 - if (executor == directExecutor()) { - this.callExecutor = new SerializeReentrantCallsDirectExecutor(); - callExecutorIsDirect = true; - } else { - this.callExecutor = new SerializingExecutor(executor); - callExecutorIsDirect = false; - } - this.channelCallsTracer = channelCallsTracer; - // Propagate the context from the thread which initiated the call to all callbacks. - this.context = Context.current(); - this.unaryRequest = - method.getType() == MethodType.UNARY || method.getType() == MethodType.SERVER_STREAMING; - this.callOptions = callOptions; - this.clientStreamProvider = clientStreamProvider; - this.deadlineCancellationExecutor = deadlineCancellationExecutor; - PerfMark.event("ClientCall.", tag); - } - - private final class ContextCancellationListener implements CancellationListener { - @Override - public void cancelled(Context context) { - stream.cancel(statusFromCancelled(context)); - } - } - - /** Provider of {@link ClientStream}s. */ - interface ClientStreamProvider { - ClientStream newStream( - MethodDescriptor method, CallOptions callOptions, Metadata headers, Context context); - } - - ClientCallImpl setFullStreamDecompression(boolean fullStreamDecompression) { - this.fullStreamDecompression = fullStreamDecompression; - return this; - } - - ClientCallImpl setDecompressorRegistry(DecompressorRegistry decompressorRegistry) { - this.decompressorRegistry = decompressorRegistry; - return this; - } - - ClientCallImpl setCompressorRegistry(CompressorRegistry compressorRegistry) { - this.compressorRegistry = compressorRegistry; - return this; - } - - @VisibleForTesting - static void prepareHeaders( - Metadata headers, - DecompressorRegistry decompressorRegistry, - Compressor compressor, - boolean fullStreamDecompression) { - headers.discardAll(CONTENT_LENGTH_KEY); - headers.discardAll(MESSAGE_ENCODING_KEY); - if (compressor != Codec.Identity.NONE) { - headers.put(MESSAGE_ENCODING_KEY, compressor.getMessageEncoding()); - } - - headers.discardAll(MESSAGE_ACCEPT_ENCODING_KEY); - byte[] advertisedEncodings = - InternalDecompressorRegistry.getRawAdvertisedMessageEncodings(decompressorRegistry); - if (advertisedEncodings.length != 0) { - headers.put(MESSAGE_ACCEPT_ENCODING_KEY, advertisedEncodings); - } - - headers.discardAll(CONTENT_ENCODING_KEY); - headers.discardAll(CONTENT_ACCEPT_ENCODING_KEY); - if (fullStreamDecompression) { - headers.put(CONTENT_ACCEPT_ENCODING_KEY, FULL_STREAM_DECOMPRESSION_ENCODINGS); - } - } - - @Override - public void start(Listener observer, Metadata headers) { - PerfMark.startTask("ClientCall.start", tag); - Histogram.Timer start = perfmarkClientCallImplDuration.labels("ClientCall.start").startTimer(); - try { - startInternal(observer, headers); - } finally { - PerfMark.stopTask("ClientCall.start", tag); - start.observeDuration(); - } - } - - private void startInternal(Listener observer, Metadata headers) { - checkState(stream == null, "Already started"); - checkState(!cancelCalled, "call was cancelled"); - checkNotNull(observer, "observer"); - checkNotNull(headers, "headers"); - - if (context.isCancelled()) { - // Context is already cancelled so no need to create a real stream, just notify the observer - // of cancellation via callback on the executor - stream = NoopClientStream.INSTANCE; - final Listener finalObserver = observer; - class ClosedByContext extends ContextRunnable { - ClosedByContext() { - super(context); - } - - @Override - public void runInContext() { - closeObserver(finalObserver, statusFromCancelled(context), new Metadata()); - } - } - - callExecutor.execute(new ClosedByContext()); - return; - } - applyMethodConfig(); - final String compressorName = callOptions.getCompressor(); - Compressor compressor; - if (compressorName != null) { - compressor = compressorRegistry.lookupCompressor(compressorName); - if (compressor == null) { - stream = NoopClientStream.INSTANCE; - final Listener finalObserver = observer; - class ClosedByNotFoundCompressor extends ContextRunnable { - ClosedByNotFoundCompressor() { - super(context); - } - - @Override - public void runInContext() { - closeObserver( - finalObserver, - Status.INTERNAL.withDescription( - String.format("Unable to find compressor by name %s", compressorName)), - new Metadata()); - } - } - - callExecutor.execute(new ClosedByNotFoundCompressor()); - return; - } - } else { - compressor = Codec.Identity.NONE; - } - prepareHeaders(headers, decompressorRegistry, compressor, fullStreamDecompression); - - Deadline effectiveDeadline = effectiveDeadline(); - boolean deadlineExceeded = effectiveDeadline != null && effectiveDeadline.isExpired(); - if (!deadlineExceeded) { - logIfContextNarrowedTimeout( - effectiveDeadline, context.getDeadline(), callOptions.getDeadline()); - stream = clientStreamProvider.newStream(method, callOptions, headers, context); - } else { - ClientStreamTracer[] tracers = - GrpcUtil.getClientStreamTracers(callOptions, headers, 0, false); - stream = - new FailingClientStream( - DEADLINE_EXCEEDED.withDescription( - "ClientCall started after deadline exceeded: " + effectiveDeadline), - tracers); - } - - if (callExecutorIsDirect) { - stream.optimizeForDirectExecutor(); - } - if (callOptions.getAuthority() != null) { - stream.setAuthority(callOptions.getAuthority()); - } - if (callOptions.getMaxInboundMessageSize() != null) { - stream.setMaxInboundMessageSize(callOptions.getMaxInboundMessageSize()); - } - if (callOptions.getMaxOutboundMessageSize() != null) { - stream.setMaxOutboundMessageSize(callOptions.getMaxOutboundMessageSize()); - } - if (effectiveDeadline != null) { - stream.setDeadline(effectiveDeadline); - } - stream.setCompressor(compressor); - if (fullStreamDecompression) { - stream.setFullStreamDecompression(fullStreamDecompression); - } - stream.setDecompressorRegistry(decompressorRegistry); - channelCallsTracer.reportCallStarted(); - stream.start(new ClientStreamListenerImpl(observer)); - - // Delay any sources of cancellation after start(), because most of the transports are broken if - // they receive cancel before start. Issue #1343 has more details - - // Propagate later Context cancellation to the remote side. - context.addListener(cancellationListener, directExecutor()); - if (effectiveDeadline != null - // If the context has the effective deadline, we don't need to schedule an extra task. - && !effectiveDeadline.equals(context.getDeadline()) - // If the channel has been terminated, we don't need to schedule an extra task. - && deadlineCancellationExecutor != null) { - deadlineCancellationFuture = startDeadlineTimer(effectiveDeadline); - } - if (cancelListenersShouldBeRemoved) { - // Race detected! ClientStreamListener.closed may have been called before - // deadlineCancellationFuture was set / context listener added, thereby preventing the future - // and listener from being cancelled. Go ahead and cancel again, just to be sure it - // was cancelled. - removeContextListenerAndCancelDeadlineFuture(); - } - } - - private void applyMethodConfig() { - MethodInfo info = callOptions.getOption(MethodInfo.KEY); - if (info == null) { - return; - } - if (info.timeoutNanos != null) { - Deadline newDeadline = Deadline.after(info.timeoutNanos, TimeUnit.NANOSECONDS); - Deadline existingDeadline = callOptions.getDeadline(); - // If the new deadline is sooner than the existing deadline, swap them. - if (existingDeadline == null || newDeadline.compareTo(existingDeadline) < 0) { - callOptions = callOptions.withDeadline(newDeadline); - } - } - if (info.waitForReady != null) { - callOptions = - info.waitForReady ? callOptions.withWaitForReady() : callOptions.withoutWaitForReady(); - } - if (info.maxInboundMessageSize != null) { - Integer existingLimit = callOptions.getMaxInboundMessageSize(); - if (existingLimit != null) { - callOptions = - callOptions.withMaxInboundMessageSize( - Math.min(existingLimit, info.maxInboundMessageSize)); - } else { - callOptions = callOptions.withMaxInboundMessageSize(info.maxInboundMessageSize); - } - } - if (info.maxOutboundMessageSize != null) { - Integer existingLimit = callOptions.getMaxOutboundMessageSize(); - if (existingLimit != null) { - callOptions = - callOptions.withMaxOutboundMessageSize( - Math.min(existingLimit, info.maxOutboundMessageSize)); - } else { - callOptions = callOptions.withMaxOutboundMessageSize(info.maxOutboundMessageSize); - } - } - } - - private static void logIfContextNarrowedTimeout( - Deadline effectiveDeadline, - @Nullable Deadline outerCallDeadline, - @Nullable Deadline callDeadline) { - if (!log.isLoggable(Level.FINE) - || effectiveDeadline == null - || !effectiveDeadline.equals(outerCallDeadline)) { - return; - } - - long effectiveTimeout = max(0, effectiveDeadline.timeRemaining(TimeUnit.NANOSECONDS)); - StringBuilder builder = - new StringBuilder( - String.format( - Locale.US, - "Call timeout set to '%d' ns, due to context deadline.", - effectiveTimeout)); - if (callDeadline == null) { - builder.append(" Explicit call timeout was not set."); - } else { - long callTimeout = callDeadline.timeRemaining(TimeUnit.NANOSECONDS); - builder.append(String.format(Locale.US, " Explicit call timeout was '%d' ns.", callTimeout)); - } - - log.fine(builder.toString()); - } - - private void removeContextListenerAndCancelDeadlineFuture() { - context.removeListener(cancellationListener); - ScheduledFuture f = deadlineCancellationFuture; - if (f != null) { - f.cancel(false); - } - } - - private class DeadlineTimer implements Runnable { - private final long remainingNanos; - - DeadlineTimer(long remainingNanos) { - this.remainingNanos = remainingNanos; - } - - @Override - public void run() { - InsightBuilder insight = new InsightBuilder(); - stream.appendTimeoutInsight(insight); - // DelayedStream.cancel() is safe to call from a thread that is different from where the - // stream is created. - long seconds = Math.abs(remainingNanos) / TimeUnit.SECONDS.toNanos(1); - long nanos = Math.abs(remainingNanos) % TimeUnit.SECONDS.toNanos(1); - - StringBuilder buf = new StringBuilder(); - buf.append("deadline exceeded after "); - if (remainingNanos < 0) { - buf.append('-'); - } - buf.append(seconds); - buf.append(String.format(Locale.US, ".%09d", nanos)); - buf.append("s. "); - buf.append(insight); - stream.cancel(DEADLINE_EXCEEDED.augmentDescription(buf.toString())); - } - } - - private ScheduledFuture startDeadlineTimer(Deadline deadline) { - long remainingNanos = deadline.timeRemaining(TimeUnit.NANOSECONDS); - return deadlineCancellationExecutor.schedule( - new LogExceptionRunnable(new DeadlineTimer(remainingNanos)), - remainingNanos, - TimeUnit.NANOSECONDS); - } - - @Nullable - private Deadline effectiveDeadline() { - // Call options and context are immutable, so we don't need to cache the deadline. - return min(callOptions.getDeadline(), context.getDeadline()); - } - - @Nullable - private static Deadline min(@Nullable Deadline deadline0, @Nullable Deadline deadline1) { - if (deadline0 == null) { - return deadline1; - } - if (deadline1 == null) { - return deadline0; - } - return deadline0.minimum(deadline1); - } - - @Override - public void request(int numMessages) { - PerfMark.startTask("ClientCall.request", tag); - Histogram.Timer request = - perfmarkClientCallImplDuration.labels("ClientCall.request").startTimer(); - try { - checkState(stream != null, "Not started"); - checkArgument(numMessages >= 0, "Number requested must be non-negative"); - stream.request(numMessages); - } finally { - PerfMark.stopTask("ClientCall.request", tag); - request.observeDuration(); - } - } - - @Override - public void cancel(@Nullable String message, @Nullable Throwable cause) { - PerfMark.startTask("ClientCall.cancel", tag); - Histogram.Timer cancel = - perfmarkClientCallImplDuration.labels("ClientCall.cancel").startTimer(); - try { - cancelInternal(message, cause); - } finally { - PerfMark.stopTask("ClientCall.cancel", tag); - cancel.observeDuration(); - } - } - - private void cancelInternal(@Nullable String message, @Nullable Throwable cause) { - if (message == null && cause == null) { - cause = new CancellationException("Cancelled without a message or cause"); - log.log(Level.WARNING, "Cancelling without a message or cause is suboptimal", cause); - } - if (cancelCalled) { - return; - } - cancelCalled = true; - try { - // Cancel is called in exception handling cases, so it may be the case that the - // stream was never successfully created or start has never been called. - if (stream != null) { - Status status = Status.CANCELLED; - if (message != null) { - status = status.withDescription(message); - } else { - status = status.withDescription("Call cancelled without message"); - } - if (cause != null) { - status = status.withCause(cause); - } - stream.cancel(status); - } - } finally { - removeContextListenerAndCancelDeadlineFuture(); - } - } - - @Override - public void halfClose() { - PerfMark.startTask("ClientCall.halfClose", tag); - Histogram.Timer halfClose = - perfmarkClientCallImplDuration.labels("ClientCall.halfClose").startTimer(); - try { - halfCloseInternal(); - } finally { - PerfMark.stopTask("ClientCall.halfClose", tag); - halfClose.observeDuration(); - } - } - - private void halfCloseInternal() { - checkState(stream != null, "Not started"); - checkState(!cancelCalled, "call was cancelled"); - checkState(!halfCloseCalled, "call already half-closed"); - halfCloseCalled = true; - stream.halfClose(); - } - - @Override - public void sendMessage(ReqT message) { - PerfMark.startTask("ClientCall.sendMessage", tag); - Histogram.Timer sendMessage = - perfmarkClientCallImplDuration.labels("ClientCall.sendMessage").startTimer(); - try { - sendMessageInternal(message); - } finally { - PerfMark.stopTask("ClientCall.sendMessage", tag); - sendMessage.observeDuration(); - } - } - - private void sendMessageInternal(ReqT message) { - checkState(stream != null, "Not started"); - checkState(!cancelCalled, "call was cancelled"); - checkState(!halfCloseCalled, "call was half-closed"); - try { - if (stream instanceof RetriableStream) { - @SuppressWarnings("unchecked") - RetriableStream retriableStream = (RetriableStream) stream; - retriableStream.sendMessage(message); - } else { - stream.writeMessage(method.streamRequest(message)); - } - } catch (RuntimeException e) { - stream.cancel(Status.CANCELLED.withCause(e).withDescription("Failed to stream message")); - return; - } catch (Error e) { - stream.cancel(Status.CANCELLED.withDescription("Client sendMessage() failed with Error")); - throw e; - } - // For unary requests, we don't flush since we know that halfClose should be coming soon. This - // allows us to piggy-back the END_STREAM=true on the last message frame without opening the - // possibility of broken applications forgetting to call halfClose without noticing. - if (!unaryRequest) { - stream.flush(); - } - } - - @Override - public void setMessageCompression(boolean enabled) { - checkState(stream != null, "Not started"); - stream.setMessageCompression(enabled); - } - - @Override - public boolean isReady() { - if (halfCloseCalled) { - return false; - } - return stream.isReady(); - } - - @Override - public Attributes getAttributes() { - if (stream != null) { - return stream.getAttributes(); - } - return Attributes.EMPTY; - } - - private void closeObserver(Listener observer, Status status, Metadata trailers) { - observer.onClose(status, trailers); - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this).add("method", method).toString(); - } - - private class ClientStreamListenerImpl implements ClientStreamListener { - private final Listener observer; - private Status exceptionStatus; - - public ClientStreamListenerImpl(Listener observer) { - this.observer = checkNotNull(observer, "observer"); - } - - /** - * Cancels call and schedules onClose() notification. May only be called from the application - * thread. - */ - private void exceptionThrown(Status status) { - // Since each RPC can have its own executor, we can only call onClose() when we are sure there - // will be no further callbacks. We set the status here and overwrite the onClose() details - // when it arrives. - exceptionStatus = status; - stream.cancel(status); - } - - @Override - public void headersRead(final Metadata headers) { - PerfMark.startTask("ClientStreamListener.headersRead", tag); - Histogram.Timer headersRead = - perfmarkClientCallImplDuration.labels("ClientStreamListener.headersRead").startTimer(); - final Link link = PerfMark.linkOut(); - - final class HeadersRead extends ContextRunnable { - HeadersRead() { - super(context); - } - - @Override - public void runInContext() { - PerfMark.startTask("ClientCall$Listener.headersRead", tag); - Histogram.Timer headersRead = - perfmarkClientCallImplDuration.labels("ClientCall$Listener.headersRead").startTimer(); - PerfMark.linkIn(link); - try { - runInternal(); - } finally { - PerfMark.stopTask("ClientCall$Listener.headersRead", tag); - headersRead.observeDuration(); - } - } - - private void runInternal() { - if (exceptionStatus != null) { - return; - } - try { - observer.onHeaders(headers); - } catch (Throwable t) { - exceptionThrown( - Status.CANCELLED.withCause(t).withDescription("Failed to read headers")); - } - } - } - - try { - callExecutor.execute(new HeadersRead()); - } finally { - PerfMark.stopTask("ClientStreamListener.headersRead", tag); - headersRead.observeDuration(); - } - } - - @Override - public void messagesAvailable(final MessageProducer producer) { - PerfMark.startTask("ClientStreamListener.messagesAvailable", tag); - Histogram.Timer messagesAvailable = - perfmarkClientCallImplDuration - .labels("ClientStreamListener.messagesAvailable") - .startTimer(); - final Link link = PerfMark.linkOut(); - - final class MessagesAvailable extends ContextRunnable { - MessagesAvailable() { - super(context); - } - - @Override - public void runInContext() { - PerfMark.startTask("ClientCall$Listener.messagesAvailable", tag); - Histogram.Timer messagesAvailable = - perfmarkClientCallImplDuration - .labels("ClientCall$Listener.messagesAvailable") - .startTimer(); - PerfMark.linkIn(link); - try { - runInternal(); - } finally { - PerfMark.stopTask("ClientCall$Listener.messagesAvailable", tag); - messagesAvailable.observeDuration(); - } - } - - private void runInternal() { - if (exceptionStatus != null) { - GrpcUtil.closeQuietly(producer); - return; - } - try { - InputStream message; - while ((message = producer.next()) != null) { - try { - observer.onMessage(method.parseResponse(message)); - } catch (Throwable t) { - GrpcUtil.closeQuietly(message); - throw t; - } - message.close(); - } - } catch (Throwable t) { - GrpcUtil.closeQuietly(producer); - exceptionThrown( - Status.CANCELLED.withCause(t).withDescription("Failed to read message.")); - } - } - } - - try { - callExecutor.execute(new MessagesAvailable()); - } finally { - PerfMark.stopTask("ClientStreamListener.messagesAvailable", tag); - messagesAvailable.observeDuration(); - } - } - - @Override - public void closed(Status status, RpcProgress rpcProgress, Metadata trailers) { - PerfMark.startTask("ClientStreamListener.closed", tag); - Histogram.Timer closed = - perfmarkClientCallImplDuration.labels("ClientStreamListener.closed").startTimer(); - try { - closedInternal(status, rpcProgress, trailers); - } finally { - PerfMark.stopTask("ClientStreamListener.closed", tag); - closed.observeDuration(); - } - } - - private void closedInternal( - Status status, @SuppressWarnings("unused") RpcProgress rpcProgress, Metadata trailers) { - Deadline deadline = effectiveDeadline(); - if (status.getCode() == Status.Code.CANCELLED && deadline != null) { - // When the server's deadline expires, it can only reset the stream with CANCEL and no - // description. Since our timer may be delayed in firing, we double-check the deadline and - // turn the failure into the likely more helpful DEADLINE_EXCEEDED status. - if (deadline.isExpired()) { - InsightBuilder insight = new InsightBuilder(); - stream.appendTimeoutInsight(insight); - status = - DEADLINE_EXCEEDED.augmentDescription( - "ClientCall was cancelled at or after deadline. " + insight); - // Replace trailers to prevent mixing sources of status and trailers. - trailers = new Metadata(); - } - } - final Status savedStatus = status; - final Metadata savedTrailers = trailers; - final Link link = PerfMark.linkOut(); - final class StreamClosed extends ContextRunnable { - StreamClosed() { - super(context); - } - - @Override - public void runInContext() { - PerfMark.startTask("ClientCall$Listener.onClose", tag); - Histogram.Timer onClose = - perfmarkClientCallImplDuration.labels("ClientCall$Listener.onClose").startTimer(); - PerfMark.linkIn(link); - try { - runInternal(); - } finally { - PerfMark.stopTask("ClientCall$Listener.onClose", tag); - onClose.observeDuration(); - } - } - - private void runInternal() { - Status status = savedStatus; - Metadata trailers = savedTrailers; - if (exceptionStatus != null) { - // Ideally exceptionStatus == savedStatus, as exceptionStatus was passed to cancel(). - // However the cancel is racy and this closed() may have already been queued when the - // cancellation occurred. Since other calls like onMessage() will throw away data if - // exceptionStatus != null, it is semantically essential that we _not_ use a status - // provided by the server. - status = exceptionStatus; - // Replace trailers to prevent mixing sources of status and trailers. - trailers = new Metadata(); - } - cancelListenersShouldBeRemoved = true; - try { - closeObserver(observer, status, trailers); - } finally { - removeContextListenerAndCancelDeadlineFuture(); - channelCallsTracer.reportCallEnded(status.isOk()); - } - } - } - - callExecutor.execute(new StreamClosed()); - } - - @Override - public void onReady() { - if (method.getType().clientSendsOneMessage()) { - return; - } - - PerfMark.startTask("ClientStreamListener.onReady", tag); - Histogram.Timer onReady = - perfmarkClientCallImplDuration.labels("ClientStreamListener.onReady").startTimer(); - final Link link = PerfMark.linkOut(); - - final class StreamOnReady extends ContextRunnable { - StreamOnReady() { - super(context); - } - - @Override - public void runInContext() { - PerfMark.startTask("ClientCall$Listener.onReady", tag); - Histogram.Timer onReady = - perfmarkClientCallImplDuration.labels("ClientCall$Listener.onReady").startTimer(); - PerfMark.linkIn(link); - try { - runInternal(); - } finally { - PerfMark.stopTask("ClientCall$Listener.onReady", tag); - onReady.observeDuration(); - } - } - - private void runInternal() { - if (exceptionStatus != null) { - return; - } - try { - observer.onReady(); - } catch (Throwable t) { - exceptionThrown( - Status.CANCELLED.withCause(t).withDescription("Failed to call onReady.")); - } - } - } - - try { - callExecutor.execute(new StreamOnReady()); - } finally { - PerfMark.stopTask("ClientStreamListener.onReady", tag); - onReady.observeDuration(); - } - } - } -} diff --git a/src/main/java/io/grpc/netty/NettyChannelBuilder.java b/src/main/java/io/grpc/netty/NettyChannelBuilder.java deleted file mode 100644 index 4975e887289..00000000000 --- a/src/main/java/io/grpc/netty/NettyChannelBuilder.java +++ /dev/null @@ -1,774 +0,0 @@ -/* - * Copyright 2014 The gRPC Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * 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. - */ - -package io.grpc.netty; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; -import static io.grpc.internal.GrpcUtil.DEFAULT_KEEPALIVE_TIMEOUT_NANOS; -import static io.grpc.internal.GrpcUtil.KEEPALIVE_TIME_NANOS_DISABLED; - -import com.google.common.annotations.VisibleForTesting; -import com.google.errorprone.annotations.CanIgnoreReturnValue; -import io.grpc.Attributes; -import io.grpc.CallCredentials; -import io.grpc.ChannelCredentials; -import io.grpc.ChannelLogger; -import io.grpc.EquivalentAddressGroup; -import io.grpc.ExperimentalApi; -import io.grpc.HttpConnectProxiedSocketAddress; -import io.grpc.Internal; -import io.grpc.ManagedChannelBuilder; -import io.grpc.internal.AbstractManagedChannelImplBuilder; -import io.grpc.internal.AtomicBackoff; -import io.grpc.internal.ClientTransportFactory; -import io.grpc.internal.ConnectionClientTransport; -import io.grpc.internal.FixedObjectPool; -import io.grpc.internal.GrpcUtil; -import io.grpc.internal.KeepAliveManager; -import io.grpc.internal.ManagedChannelImplBuilder; -import io.grpc.internal.ManagedChannelImplBuilder.ChannelBuilderDefaultPortProvider; -import io.grpc.internal.ManagedChannelImplBuilder.ClientTransportFactoryBuilder; -import io.grpc.internal.ObjectPool; -import io.grpc.internal.SharedResourcePool; -import io.grpc.internal.TransportTracer; -import io.grpc.netty.ProtocolNegotiators.FromChannelCredentialsResult; -import io.netty.channel.Channel; -import io.netty.channel.ChannelFactory; -import io.netty.channel.ChannelOption; -import io.netty.channel.EventLoopGroup; -import io.netty.channel.ReflectiveChannelFactory; -import io.netty.channel.socket.nio.NioSocketChannel; -import io.netty.handler.ssl.SslContext; -import java.net.InetSocketAddress; -import java.net.SocketAddress; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.Executor; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import javax.annotation.CheckReturnValue; -import javax.annotation.Nullable; -import javax.net.ssl.SSLException; - -/** A builder to help simplify construction of channels using the Netty transport. */ -@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1784") -@CanIgnoreReturnValue -public final class NettyChannelBuilder - extends AbstractManagedChannelImplBuilder { - - // 1MiB. - public static final int DEFAULT_FLOW_CONTROL_WINDOW = 1024 * 1024; - private static final boolean DEFAULT_AUTO_FLOW_CONTROL; - - private static final long AS_LARGE_AS_INFINITE = TimeUnit.DAYS.toNanos(1000L); - - private static final ChannelFactory DEFAULT_CHANNEL_FACTORY = - new ReflectiveChannelFactory<>(Utils.DEFAULT_CLIENT_CHANNEL_TYPE); - private static final ObjectPool DEFAULT_EVENT_LOOP_GROUP_POOL = - SharedResourcePool.forResource(Utils.DEFAULT_WORKER_EVENT_LOOP_GROUP); - - static { - String autoFlowControl = System.getenv("GRPC_EXPERIMENTAL_AUTOFLOWCONTROL"); - if (autoFlowControl == null) { - autoFlowControl = "true"; - } - DEFAULT_AUTO_FLOW_CONTROL = Boolean.parseBoolean(autoFlowControl); - } - - private final ManagedChannelImplBuilder managedChannelImplBuilder; - private TransportTracer.Factory transportTracerFactory = TransportTracer.getDefaultFactory(); - private final Map, Object> channelOptions = new HashMap<>(); - private ChannelFactory channelFactory = DEFAULT_CHANNEL_FACTORY; - private ObjectPool eventLoopGroupPool = DEFAULT_EVENT_LOOP_GROUP_POOL; - private boolean autoFlowControl = DEFAULT_AUTO_FLOW_CONTROL; - private int flowControlWindow = DEFAULT_FLOW_CONTROL_WINDOW; - private int maxInboundMessageSize = GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE; - private int maxHeaderListSize = GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE; - private long keepAliveTimeNanos = KEEPALIVE_TIME_NANOS_DISABLED; - private long keepAliveTimeoutNanos = DEFAULT_KEEPALIVE_TIMEOUT_NANOS; - private boolean keepAliveWithoutCalls; - private ProtocolNegotiator.ClientFactory protocolNegotiatorFactory = - new DefaultProtocolNegotiator(); - private final boolean freezeProtocolNegotiatorFactory; - private LocalSocketPicker localSocketPicker; - - /** - * If true, indicates that the transport may use the GET method for RPCs, and may include the - * request body in the query params. - */ - private final boolean useGetForSafeMethods = false; - - /** - * Creates a new builder with the given server address. This factory method is primarily intended - * for using Netty Channel types other than SocketChannel. {@link #forAddress(String, int)} should - * generally be preferred over this method, since that API permits delaying DNS lookups and - * noticing changes to DNS. If an unresolved InetSocketAddress is passed in, then it will remain - * unresolved. - */ - @CheckReturnValue - public static NettyChannelBuilder forAddress(SocketAddress serverAddress) { - return new NettyChannelBuilder(serverAddress); - } - - /** Creates a new builder with the given host and port. */ - @CheckReturnValue - public static NettyChannelBuilder forAddress(String host, int port) { - return forTarget(GrpcUtil.authorityFromHostAndPort(host, port)); - } - - /** Creates a new builder with the given host and port. */ - @CheckReturnValue - public static NettyChannelBuilder forAddress(String host, int port, ChannelCredentials creds) { - return forTarget(GrpcUtil.authorityFromHostAndPort(host, port), creds); - } - - /** - * Creates a new builder with the given target string that will be resolved by {@link - * io.grpc.NameResolver}. - */ - @CheckReturnValue - public static NettyChannelBuilder forTarget(String target) { - return new NettyChannelBuilder(target); - } - - /** - * Creates a new builder with the given target string that will be resolved by {@link - * io.grpc.NameResolver}. - */ - @CheckReturnValue - public static NettyChannelBuilder forTarget(String target, ChannelCredentials creds) { - FromChannelCredentialsResult result = ProtocolNegotiators.from(creds); - if (result.error != null) { - throw new IllegalArgumentException(result.error); - } - return new NettyChannelBuilder(target, creds, result.callCredentials, result.negotiator); - } - - private final class NettyChannelTransportFactoryBuilder implements ClientTransportFactoryBuilder { - @Override - public ClientTransportFactory buildClientTransportFactory() { - return buildTransportFactory(); - } - } - - private final class NettyChannelDefaultPortProvider implements ChannelBuilderDefaultPortProvider { - @Override - public int getDefaultPort() { - return protocolNegotiatorFactory.getDefaultPort(); - } - } - - @CheckReturnValue - NettyChannelBuilder(String target) { - managedChannelImplBuilder = - new ManagedChannelImplBuilder( - target, - new NettyChannelTransportFactoryBuilder(), - new NettyChannelDefaultPortProvider()); - this.freezeProtocolNegotiatorFactory = false; - } - - NettyChannelBuilder( - String target, - ChannelCredentials channelCreds, - CallCredentials callCreds, - ProtocolNegotiator.ClientFactory negotiator) { - managedChannelImplBuilder = - new ManagedChannelImplBuilder( - target, - channelCreds, - callCreds, - new NettyChannelTransportFactoryBuilder(), - new NettyChannelDefaultPortProvider()); - this.protocolNegotiatorFactory = checkNotNull(negotiator, "negotiator"); - this.freezeProtocolNegotiatorFactory = true; - } - - @CheckReturnValue - NettyChannelBuilder(SocketAddress address) { - managedChannelImplBuilder = - new ManagedChannelImplBuilder( - address, - getAuthorityFromAddress(address), - new NettyChannelTransportFactoryBuilder(), - new NettyChannelDefaultPortProvider()); - this.freezeProtocolNegotiatorFactory = false; - } - - @Internal - @Override - protected ManagedChannelBuilder delegate() { - return managedChannelImplBuilder; - } - - @CheckReturnValue - private static String getAuthorityFromAddress(SocketAddress address) { - if (address instanceof InetSocketAddress) { - InetSocketAddress inetAddress = (InetSocketAddress) address; - return GrpcUtil.authorityFromHostAndPort(inetAddress.getHostString(), inetAddress.getPort()); - } else { - return address.toString(); - } - } - - /** - * Specifies the channel type to use, by default we use {@code EpollSocketChannel} if available, - * otherwise using {@link NioSocketChannel}. - * - *

You either use this or {@link #channelFactory(io.netty.channel.ChannelFactory)} if your - * {@link Channel} implementation has no no-args constructor. - * - *

It's an optional parameter. If the user has not provided an Channel type or ChannelFactory - * when the channel is built, the builder will use the default one which is static. - * - *

You must also provide corresponding {@link #eventLoopGroup(EventLoopGroup)}. For example, - * {@link NioSocketChannel} must use {@link io.netty.channel.nio.NioEventLoopGroup}, otherwise - * your application won't start. - */ - public NettyChannelBuilder channelType(Class channelType) { - checkNotNull(channelType, "channelType"); - return channelFactory(new ReflectiveChannelFactory<>(channelType)); - } - - /** - * Specifies the {@link ChannelFactory} to create {@link Channel} instances. This method is - * usually only used if the specific {@code Channel} requires complex logic which requires - * additional information to create the {@code Channel}. Otherwise, recommend to use {@link - * #channelType(Class)}. - * - *

It's an optional parameter. If the user has not provided an Channel type or ChannelFactory - * when the channel is built, the builder will use the default one which is static. - * - *

You must also provide corresponding {@link #eventLoopGroup(EventLoopGroup)}. For example, - * {@link NioSocketChannel} based {@link ChannelFactory} must use {@link - * io.netty.channel.nio.NioEventLoopGroup}, otherwise your application won't start. - */ - public NettyChannelBuilder channelFactory(ChannelFactory channelFactory) { - this.channelFactory = checkNotNull(channelFactory, "channelFactory"); - return this; - } - - /** - * Specifies a channel option. As the underlying channel as well as network implementation may - * ignore this value applications should consider it a hint. - */ - public NettyChannelBuilder withOption(ChannelOption option, T value) { - channelOptions.put(option, value); - return this; - } - - /** - * Sets the negotiation type for the HTTP/2 connection. - * - *

Default: TLS - */ - public NettyChannelBuilder negotiationType(NegotiationType type) { - checkState( - !freezeProtocolNegotiatorFactory, "Cannot change security when using ChannelCredentials"); - if (!(protocolNegotiatorFactory instanceof DefaultProtocolNegotiator)) { - // Do nothing for compatibility - return this; - } - ((DefaultProtocolNegotiator) protocolNegotiatorFactory).negotiationType = type; - return this; - } - - /** - * Provides an EventGroupLoop to be used by the netty transport. - * - *

It's an optional parameter. If the user has not provided an EventGroupLoop when the channel - * is built, the builder will use the default one which is static. - * - *

You must also provide corresponding {@link #channelType(Class)} or {@link - * #channelFactory(ChannelFactory)} corresponding to the given {@code EventLoopGroup}. For - * example, {@link io.netty.channel.nio.NioEventLoopGroup} requires {@link NioSocketChannel} - * - *

The channel won't take ownership of the given EventLoopGroup. It's caller's responsibility - * to shut it down when it's desired. - */ - public NettyChannelBuilder eventLoopGroup(@Nullable EventLoopGroup eventLoopGroup) { - if (eventLoopGroup != null) { - return eventLoopGroupPool(new FixedObjectPool<>(eventLoopGroup)); - } - return eventLoopGroupPool(DEFAULT_EVENT_LOOP_GROUP_POOL); - } - - NettyChannelBuilder eventLoopGroupPool(ObjectPool eventLoopGroupPool) { - this.eventLoopGroupPool = checkNotNull(eventLoopGroupPool, "eventLoopGroupPool"); - return this; - } - - /** - * SSL/TLS context to use instead of the system default. It must have been configured with {@link - * GrpcSslContexts}, but options could have been overridden. - */ - public NettyChannelBuilder sslContext(SslContext sslContext) { - checkState( - !freezeProtocolNegotiatorFactory, "Cannot change security when using ChannelCredentials"); - if (sslContext != null) { - checkArgument(sslContext.isClient(), "Server SSL context can not be used for client channel"); - GrpcSslContexts.ensureAlpnAndH2Enabled(sslContext.applicationProtocolNegotiator()); - } - if (!(protocolNegotiatorFactory instanceof DefaultProtocolNegotiator)) { - // Do nothing for compatibility - return this; - } - ((DefaultProtocolNegotiator) protocolNegotiatorFactory).sslContext = sslContext; - return this; - } - - /** - * Sets the initial flow control window in bytes. Setting initial flow control window enables auto - * flow control tuning using bandwidth-delay product algorithm. To disable auto flow control - * tuning, use {@link #flowControlWindow(int)}. By default, auto flow control is enabled with - * initial flow control window size of {@link #DEFAULT_FLOW_CONTROL_WINDOW}. - */ - public NettyChannelBuilder initialFlowControlWindow(int initialFlowControlWindow) { - checkArgument(initialFlowControlWindow > 0, "initialFlowControlWindow must be positive"); - this.flowControlWindow = initialFlowControlWindow; - this.autoFlowControl = true; - return this; - } - - /** - * Sets the flow control window in bytes. Setting flowControlWindow disables auto flow control - * tuning; use {@link #initialFlowControlWindow(int)} to enable auto flow control tuning. If not - * called, the default value is {@link #DEFAULT_FLOW_CONTROL_WINDOW}) with auto flow control - * tuning. - */ - public NettyChannelBuilder flowControlWindow(int flowControlWindow) { - checkArgument(flowControlWindow > 0, "flowControlWindow must be positive"); - this.flowControlWindow = flowControlWindow; - this.autoFlowControl = false; - return this; - } - - /** - * Sets the maximum size of header list allowed to be received. This is cumulative size of the - * headers with some overhead, as defined for HTTP/2's - * SETTINGS_MAX_HEADER_LIST_SIZE. The default is 8 KiB. - * - * @deprecated Use {@link #maxInboundMetadataSize} instead - */ - @Deprecated - public NettyChannelBuilder maxHeaderListSize(int maxHeaderListSize) { - return maxInboundMetadataSize(maxHeaderListSize); - } - - /** - * Sets the maximum size of metadata allowed to be received. This is cumulative size of the - * entries with some overhead, as defined for HTTP/2's - * SETTINGS_MAX_HEADER_LIST_SIZE. The default is 8 KiB. - * - * @param bytes the maximum size of received metadata - * @return this - * @throws IllegalArgumentException if bytes is non-positive - * @since 1.17.0 - */ - @Override - public NettyChannelBuilder maxInboundMetadataSize(int bytes) { - checkArgument(bytes > 0, "maxInboundMetadataSize must be > 0"); - this.maxHeaderListSize = bytes; - return this; - } - - /** Equivalent to using {@link #negotiationType(NegotiationType)} with {@code PLAINTEXT}. */ - @Override - public NettyChannelBuilder usePlaintext() { - negotiationType(NegotiationType.PLAINTEXT); - return this; - } - - /** Equivalent to using {@link #negotiationType(NegotiationType)} with {@code TLS}. */ - @Override - public NettyChannelBuilder useTransportSecurity() { - negotiationType(NegotiationType.TLS); - return this; - } - - /** - * {@inheritDoc} - * - * @since 1.3.0 - */ - @Override - public NettyChannelBuilder keepAliveTime(long keepAliveTime, TimeUnit timeUnit) { - checkArgument(keepAliveTime > 0L, "keepalive time must be positive"); - keepAliveTimeNanos = timeUnit.toNanos(keepAliveTime); - keepAliveTimeNanos = KeepAliveManager.clampKeepAliveTimeInNanos(keepAliveTimeNanos); - if (keepAliveTimeNanos >= AS_LARGE_AS_INFINITE) { - // Bump keepalive time to infinite. This disables keepalive. - keepAliveTimeNanos = KEEPALIVE_TIME_NANOS_DISABLED; - } - return this; - } - - /** - * {@inheritDoc} - * - * @since 1.3.0 - */ - @Override - public NettyChannelBuilder keepAliveTimeout(long keepAliveTimeout, TimeUnit timeUnit) { - checkArgument(keepAliveTimeout > 0L, "keepalive timeout must be positive"); - keepAliveTimeoutNanos = timeUnit.toNanos(keepAliveTimeout); - keepAliveTimeoutNanos = KeepAliveManager.clampKeepAliveTimeoutInNanos(keepAliveTimeoutNanos); - return this; - } - - /** - * {@inheritDoc} - * - * @since 1.3.0 - */ - @Override - public NettyChannelBuilder keepAliveWithoutCalls(boolean enable) { - keepAliveWithoutCalls = enable; - return this; - } - - /** If non-{@code null}, attempts to create connections bound to a local port. */ - public NettyChannelBuilder localSocketPicker(@Nullable LocalSocketPicker localSocketPicker) { - this.localSocketPicker = localSocketPicker; - return this; - } - - /** - * This class is meant to be overriden with a custom implementation of {@link - * #createSocketAddress}. The default implementation is a no-op. - * - * @since 1.16.0 - */ - @ExperimentalApi("https://github.com/grpc/grpc-java/issues/4917") - public static class LocalSocketPicker { - - /** - * Called by gRPC to pick local socket to bind to. This may be called multiple times. Subclasses - * are expected to override this method. - * - * @param remoteAddress the remote address to connect to. - * @param attrs the Attributes present on the {@link io.grpc.EquivalentAddressGroup} associated - * with the address. - * @return a {@link SocketAddress} suitable for binding, or else {@code null}. - * @since 1.16.0 - */ - @Nullable - public SocketAddress createSocketAddress( - SocketAddress remoteAddress, @EquivalentAddressGroup.Attr Attributes attrs) { - return null; - } - } - - /** - * Sets the maximum message size allowed for a single gRPC frame. If an inbound messages larger - * than this limit is received it will not be processed and the RPC will fail with - * RESOURCE_EXHAUSTED. - */ - @Override - public NettyChannelBuilder maxInboundMessageSize(int max) { - checkArgument(max >= 0, "negative max"); - maxInboundMessageSize = max; - return this; - } - - @CheckReturnValue - ClientTransportFactory buildTransportFactory() { - assertEventLoopAndChannelType(); - - ProtocolNegotiator negotiator = protocolNegotiatorFactory.newNegotiator(); - return new NettyTransportFactory( - negotiator, - channelFactory, - channelOptions, - eventLoopGroupPool, - autoFlowControl, - flowControlWindow, - maxInboundMessageSize, - maxHeaderListSize, - keepAliveTimeNanos, - keepAliveTimeoutNanos, - keepAliveWithoutCalls, - transportTracerFactory, - localSocketPicker, - useGetForSafeMethods); - } - - @VisibleForTesting - void assertEventLoopAndChannelType() { - boolean bothProvided = - channelFactory != DEFAULT_CHANNEL_FACTORY - && eventLoopGroupPool != DEFAULT_EVENT_LOOP_GROUP_POOL; - boolean nonProvided = - channelFactory == DEFAULT_CHANNEL_FACTORY - && eventLoopGroupPool == DEFAULT_EVENT_LOOP_GROUP_POOL; - checkState( - bothProvided || nonProvided, - "Both EventLoopGroup and ChannelType should be provided or neither should be"); - } - - @CheckReturnValue - int getDefaultPort() { - return protocolNegotiatorFactory.getDefaultPort(); - } - - @VisibleForTesting - @CheckReturnValue - static ProtocolNegotiator createProtocolNegotiatorByType( - NegotiationType negotiationType, - SslContext sslContext, - ObjectPool executorPool) { - switch (negotiationType) { - case PLAINTEXT: - return ProtocolNegotiators.plaintext(); - case PLAINTEXT_UPGRADE: - return ProtocolNegotiators.plaintextUpgrade(); - case TLS: - return ProtocolNegotiators.tls(sslContext, executorPool); - default: - throw new IllegalArgumentException("Unsupported negotiationType: " + negotiationType); - } - } - - NettyChannelBuilder disableCheckAuthority() { - this.managedChannelImplBuilder.disableCheckAuthority(); - return this; - } - - NettyChannelBuilder enableCheckAuthority() { - this.managedChannelImplBuilder.enableCheckAuthority(); - return this; - } - - void protocolNegotiatorFactory(ProtocolNegotiator.ClientFactory protocolNegotiatorFactory) { - checkState( - !freezeProtocolNegotiatorFactory, "Cannot change security when using ChannelCredentials"); - this.protocolNegotiatorFactory = - checkNotNull(protocolNegotiatorFactory, "protocolNegotiatorFactory"); - } - - void setTracingEnabled(boolean value) { - this.managedChannelImplBuilder.setTracingEnabled(value); - } - - void setStatsEnabled(boolean value) { - this.managedChannelImplBuilder.setStatsEnabled(value); - } - - void setStatsRecordStartedRpcs(boolean value) { - this.managedChannelImplBuilder.setStatsRecordStartedRpcs(value); - } - - void setStatsRecordFinishedRpcs(boolean value) { - this.managedChannelImplBuilder.setStatsRecordFinishedRpcs(value); - } - - void setStatsRecordRealTimeMetrics(boolean value) { - this.managedChannelImplBuilder.setStatsRecordRealTimeMetrics(value); - } - - @VisibleForTesting - NettyChannelBuilder setTransportTracerFactory(TransportTracer.Factory transportTracerFactory) { - this.transportTracerFactory = transportTracerFactory; - return this; - } - - private final class DefaultProtocolNegotiator implements ProtocolNegotiator.ClientFactory { - private NegotiationType negotiationType = NegotiationType.TLS; - private SslContext sslContext; - - @Override - public ProtocolNegotiator newNegotiator() { - SslContext localSslContext = sslContext; - if (negotiationType == NegotiationType.TLS && localSslContext == null) { - try { - localSslContext = GrpcSslContexts.forClient().build(); - } catch (SSLException ex) { - throw new RuntimeException(ex); - } - } - return createProtocolNegotiatorByType( - negotiationType, localSslContext, managedChannelImplBuilder.getOffloadExecutorPool()); - } - - @Override - public int getDefaultPort() { - switch (negotiationType) { - case PLAINTEXT: - case PLAINTEXT_UPGRADE: - return GrpcUtil.DEFAULT_PORT_PLAINTEXT; - case TLS: - return GrpcUtil.DEFAULT_PORT_SSL; - default: - throw new AssertionError(negotiationType + " not handled"); - } - } - } - - /** Creates Netty transports. Exposed for internal use, as it should be private. */ - @CheckReturnValue - private static final class NettyTransportFactory implements ClientTransportFactory { - private final ProtocolNegotiator protocolNegotiator; - private final ChannelFactory channelFactory; - private final Map, ?> channelOptions; - private final ObjectPool groupPool; - private final EventLoopGroup group; - private final boolean autoFlowControl; - private final int flowControlWindow; - private final int maxMessageSize; - private final int maxHeaderListSize; - private final long keepAliveTimeNanos; - private final AtomicBackoff keepAliveBackoff; - private final long keepAliveTimeoutNanos; - private final boolean keepAliveWithoutCalls; - private final TransportTracer.Factory transportTracerFactory; - private final LocalSocketPicker localSocketPicker; - private final boolean useGetForSafeMethods; - - private boolean closed; - - NettyTransportFactory( - ProtocolNegotiator protocolNegotiator, - ChannelFactory channelFactory, - Map, ?> channelOptions, - ObjectPool groupPool, - boolean autoFlowControl, - int flowControlWindow, - int maxMessageSize, - int maxHeaderListSize, - long keepAliveTimeNanos, - long keepAliveTimeoutNanos, - boolean keepAliveWithoutCalls, - TransportTracer.Factory transportTracerFactory, - LocalSocketPicker localSocketPicker, - boolean useGetForSafeMethods) { - this.protocolNegotiator = checkNotNull(protocolNegotiator, "protocolNegotiator"); - this.channelFactory = channelFactory; - this.channelOptions = new HashMap, Object>(channelOptions); - this.groupPool = groupPool; - this.group = groupPool.getObject(); - this.autoFlowControl = autoFlowControl; - this.flowControlWindow = flowControlWindow; - this.maxMessageSize = maxMessageSize; - this.maxHeaderListSize = maxHeaderListSize; - this.keepAliveTimeNanos = keepAliveTimeNanos; - this.keepAliveBackoff = new AtomicBackoff("keepalive time nanos", keepAliveTimeNanos); - this.keepAliveTimeoutNanos = keepAliveTimeoutNanos; - this.keepAliveWithoutCalls = keepAliveWithoutCalls; - this.transportTracerFactory = transportTracerFactory; - this.localSocketPicker = - localSocketPicker != null ? localSocketPicker : new LocalSocketPicker(); - this.useGetForSafeMethods = useGetForSafeMethods; - } - - @Override - public ConnectionClientTransport newClientTransport( - SocketAddress serverAddress, ClientTransportOptions options, ChannelLogger channelLogger) { - checkState(!closed, "The transport factory is closed."); - - ProtocolNegotiator localNegotiator = protocolNegotiator; - HttpConnectProxiedSocketAddress proxiedAddr = options.getHttpConnectProxiedSocketAddress(); - if (proxiedAddr != null) { - serverAddress = proxiedAddr.getTargetAddress(); - localNegotiator = - ProtocolNegotiators.httpProxy( - proxiedAddr.getProxyAddress(), - proxiedAddr.getUsername(), - proxiedAddr.getPassword(), - protocolNegotiator); - } - - final AtomicBackoff.State keepAliveTimeNanosState = keepAliveBackoff.getState(); - Runnable tooManyPingsRunnable = - new Runnable() { - @Override - public void run() { - keepAliveTimeNanosState.backoff(); - } - }; - - // TODO(carl-mastrangelo): Pass channelLogger in. - NettyClientTransport transport = - new NettyClientTransport( - serverAddress, - channelFactory, - channelOptions, - group, - localNegotiator, - autoFlowControl, - flowControlWindow, - maxMessageSize, - maxHeaderListSize, - keepAliveTimeNanosState.get(), - keepAliveTimeoutNanos, - keepAliveWithoutCalls, - options.getAuthority(), - options.getUserAgent(), - tooManyPingsRunnable, - transportTracerFactory.create(), - options.getEagAttributes(), - localSocketPicker, - channelLogger, - useGetForSafeMethods); - return transport; - } - - @Override - public ScheduledExecutorService getScheduledExecutorService() { - return group; - } - - @Override - public SwapChannelCredentialsResult swapChannelCredentials(ChannelCredentials channelCreds) { - checkNotNull(channelCreds, "channelCreds"); - FromChannelCredentialsResult result = ProtocolNegotiators.from(channelCreds); - if (result.error != null) { - return null; - } - ClientTransportFactory factory = - new NettyTransportFactory( - result.negotiator.newNegotiator(), - channelFactory, - channelOptions, - groupPool, - autoFlowControl, - flowControlWindow, - maxMessageSize, - maxHeaderListSize, - keepAliveTimeNanos, - keepAliveTimeoutNanos, - keepAliveWithoutCalls, - transportTracerFactory, - localSocketPicker, - useGetForSafeMethods); - return new SwapChannelCredentialsResult(factory, result.callCredentials); - } - - @Override - public void close() { - if (closed) { - return; - } - closed = true; - - protocolNegotiator.close(); - groupPool.returnObject(group); - } - } -} diff --git a/src/main/java/io/grpc/netty/NettyClientHandler.java b/src/main/java/io/grpc/netty/NettyClientHandler.java deleted file mode 100644 index 2248cee33ee..00000000000 --- a/src/main/java/io/grpc/netty/NettyClientHandler.java +++ /dev/null @@ -1,1145 +0,0 @@ -/* - * Copyright 2014 The gRPC Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * 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. - */ - -package io.grpc.netty; - -import static io.netty.handler.codec.http2.DefaultHttp2LocalFlowController.DEFAULT_WINDOW_UPDATE_RATIO; -import static io.netty.util.CharsetUtil.UTF_8; -import static io.netty.util.internal.ObjectUtil.checkNotNull; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Preconditions; -import com.google.common.base.Stopwatch; -import com.google.common.base.Supplier; -import io.grpc.Attributes; -import io.grpc.ChannelLogger; -import io.grpc.InternalChannelz; -import io.grpc.Metadata; -import io.grpc.Status; -import io.grpc.StatusException; -import io.grpc.internal.ClientStreamListener.RpcProgress; -import io.grpc.internal.ClientTransport.PingCallback; -import io.grpc.internal.GrpcAttributes; -import io.grpc.internal.GrpcUtil; -import io.grpc.internal.Http2Ping; -import io.grpc.internal.InUseStateAggregator; -import io.grpc.internal.KeepAliveManager; -import io.grpc.internal.TransportTracer; -import io.grpc.netty.GrpcHttp2HeadersUtils.GrpcHttp2ClientHeadersDecoder; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufUtil; -import io.netty.buffer.Unpooled; -import io.netty.channel.Channel; -import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelFutureListener; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelPromise; -import io.netty.handler.codec.http2.DecoratingHttp2FrameWriter; -import io.netty.handler.codec.http2.DefaultHttp2Connection; -import io.netty.handler.codec.http2.DefaultHttp2ConnectionDecoder; -import io.netty.handler.codec.http2.DefaultHttp2ConnectionEncoder; -import io.netty.handler.codec.http2.DefaultHttp2FrameReader; -import io.netty.handler.codec.http2.DefaultHttp2FrameWriter; -import io.netty.handler.codec.http2.DefaultHttp2LocalFlowController; -import io.netty.handler.codec.http2.DefaultHttp2RemoteFlowController; -import io.netty.handler.codec.http2.Http2CodecUtil; -import io.netty.handler.codec.http2.Http2Connection; -import io.netty.handler.codec.http2.Http2ConnectionAdapter; -import io.netty.handler.codec.http2.Http2ConnectionDecoder; -import io.netty.handler.codec.http2.Http2ConnectionEncoder; -import io.netty.handler.codec.http2.Http2Error; -import io.netty.handler.codec.http2.Http2Exception; -import io.netty.handler.codec.http2.Http2FlowController; -import io.netty.handler.codec.http2.Http2FrameAdapter; -import io.netty.handler.codec.http2.Http2FrameLogger; -import io.netty.handler.codec.http2.Http2FrameReader; -import io.netty.handler.codec.http2.Http2FrameWriter; -import io.netty.handler.codec.http2.Http2Headers; -import io.netty.handler.codec.http2.Http2HeadersDecoder; -import io.netty.handler.codec.http2.Http2InboundFrameLogger; -import io.netty.handler.codec.http2.Http2OutboundFrameLogger; -import io.netty.handler.codec.http2.Http2Settings; -import io.netty.handler.codec.http2.Http2Stream; -import io.netty.handler.codec.http2.Http2StreamVisitor; -import io.netty.handler.codec.http2.StreamBufferingEncoder; -import io.netty.handler.codec.http2.WeightedFairQueueByteDistributor; -import io.netty.handler.logging.LogLevel; -import io.perfmark.PerfMark; -import io.perfmark.Tag; -import io.prometheus.client.Histogram; -import java.nio.channels.ClosedChannelException; -import java.util.concurrent.Executor; -import java.util.logging.Level; -import java.util.logging.Logger; -import javax.annotation.Nullable; -import org.tikv.common.util.HistogramUtils; - -/** - * Client-side Netty handler for GRPC processing. All event handlers are executed entirely within - * the context of the Netty Channel thread. - */ -class NettyClientHandler extends AbstractNettyHandler { - private static final Logger logger = Logger.getLogger(NettyClientHandler.class.getName()); - - /** - * A message that simply passes through the channel without any real processing. It is useful to - * check if buffers have been drained and test the health of the channel in a single operation. - */ - static final Object NOOP_MESSAGE = new Object(); - - /** Status used when the transport has exhausted the number of streams. */ - private static final Status EXHAUSTED_STREAMS_STATUS = - Status.UNAVAILABLE.withDescription("Stream IDs have been exhausted"); - - private static final long USER_PING_PAYLOAD = 1111; - - private final Http2Connection.PropertyKey streamKey; - private final ClientTransportLifecycleManager lifecycleManager; - private final KeepAliveManager keepAliveManager; - // Returns new unstarted stopwatches - private final Supplier stopwatchFactory; - private final TransportTracer transportTracer; - private final Attributes eagAttributes; - private final String authority; - private final InUseStateAggregator inUseState = - new InUseStateAggregator() { - @Override - protected void handleInUse() { - lifecycleManager.notifyInUse(true); - } - - @Override - protected void handleNotInUse() { - lifecycleManager.notifyInUse(false); - } - }; - - private WriteQueue clientWriteQueue; - private Http2Ping ping; - private Attributes attributes; - private InternalChannelz.Security securityInfo; - private Status abruptGoAwayStatus; - private Status channelInactiveReason; - - public static final Histogram createStreamWriteHeaderDuration = - HistogramUtils.buildDuration() - .name("grpc_netty_client_stream_write_header_duration_seconds") - .help("Time taken to write headers for a stream in seconds.") - .register(); - - public static final Histogram createStreamAddListenerDuration = - HistogramUtils.buildDuration() - .name("grpc_netty_client_stream_add_listener_duration_seconds") - .help("Time taken to add listener for a stream future in seconds.") - .register(); - - public static final Histogram createStreamCreateNewFuture = - HistogramUtils.buildDuration() - .name("grpc_netty_client_stream_create_future_duration_seconds") - .help("Time taken to create new stream future in seconds.") - .register(); - - public static final Histogram perfmarkNettyClientHandlerDuration = - HistogramUtils.buildDuration() - .name("perfmark_netty_client_handler_duration_seconds") - .help("Perfmark netty client handler duration seconds") - .labelNames("type") - .register(); - - static NettyClientHandler newHandler( - ClientTransportLifecycleManager lifecycleManager, - @Nullable KeepAliveManager keepAliveManager, - boolean autoFlowControl, - int flowControlWindow, - int maxHeaderListSize, - Supplier stopwatchFactory, - Runnable tooManyPingsRunnable, - TransportTracer transportTracer, - Attributes eagAttributes, - String authority, - ChannelLogger negotiationLogger) { - Preconditions.checkArgument(maxHeaderListSize > 0, "maxHeaderListSize must be positive"); - Http2HeadersDecoder headersDecoder = new GrpcHttp2ClientHeadersDecoder(maxHeaderListSize); - Http2FrameReader frameReader = new DefaultHttp2FrameReader(headersDecoder); - Http2FrameWriter frameWriter = new DefaultHttp2FrameWriter(); - Http2Connection connection = new DefaultHttp2Connection(false); - WeightedFairQueueByteDistributor dist = new WeightedFairQueueByteDistributor(connection); - dist.allocationQuantum(16 * 1024); // Make benchmarks fast again. - DefaultHttp2RemoteFlowController controller = - new DefaultHttp2RemoteFlowController(connection, dist); - connection.remote().flowController(controller); - - return newHandler( - connection, - frameReader, - frameWriter, - lifecycleManager, - keepAliveManager, - autoFlowControl, - flowControlWindow, - maxHeaderListSize, - stopwatchFactory, - tooManyPingsRunnable, - transportTracer, - eagAttributes, - authority, - negotiationLogger); - } - - @VisibleForTesting - static NettyClientHandler newHandler( - final Http2Connection connection, - Http2FrameReader frameReader, - Http2FrameWriter frameWriter, - ClientTransportLifecycleManager lifecycleManager, - KeepAliveManager keepAliveManager, - boolean autoFlowControl, - int flowControlWindow, - int maxHeaderListSize, - Supplier stopwatchFactory, - Runnable tooManyPingsRunnable, - TransportTracer transportTracer, - Attributes eagAttributes, - String authority, - ChannelLogger negotiationLogger) { - Preconditions.checkNotNull(connection, "connection"); - Preconditions.checkNotNull(frameReader, "frameReader"); - Preconditions.checkNotNull(lifecycleManager, "lifecycleManager"); - Preconditions.checkArgument(flowControlWindow > 0, "flowControlWindow must be positive"); - Preconditions.checkArgument(maxHeaderListSize > 0, "maxHeaderListSize must be positive"); - Preconditions.checkNotNull(stopwatchFactory, "stopwatchFactory"); - Preconditions.checkNotNull(tooManyPingsRunnable, "tooManyPingsRunnable"); - Preconditions.checkNotNull(eagAttributes, "eagAttributes"); - Preconditions.checkNotNull(authority, "authority"); - - Http2FrameLogger frameLogger = new Http2FrameLogger(LogLevel.DEBUG, NettyClientHandler.class); - frameReader = new Http2InboundFrameLogger(frameReader, frameLogger); - frameWriter = new Http2OutboundFrameLogger(frameWriter, frameLogger); - - PingCountingFrameWriter pingCounter; - frameWriter = pingCounter = new PingCountingFrameWriter(frameWriter); - - StreamBufferingEncoder encoder = - new StreamBufferingEncoder(new DefaultHttp2ConnectionEncoder(connection, frameWriter)); - - // Create the local flow controller configured to auto-refill the connection window. - connection - .local() - .flowController( - new DefaultHttp2LocalFlowController(connection, DEFAULT_WINDOW_UPDATE_RATIO, true)); - - Http2ConnectionDecoder decoder = - new DefaultHttp2ConnectionDecoder(connection, encoder, frameReader); - - transportTracer.setFlowControlWindowReader( - new TransportTracer.FlowControlReader() { - final Http2FlowController local = connection.local().flowController(); - final Http2FlowController remote = connection.remote().flowController(); - - @Override - public TransportTracer.FlowControlWindows read() { - return new TransportTracer.FlowControlWindows( - local.windowSize(connection.connectionStream()), - remote.windowSize(connection.connectionStream())); - } - }); - - Http2Settings settings = new Http2Settings(); - settings.pushEnabled(false); - settings.initialWindowSize(flowControlWindow); - settings.maxConcurrentStreams(0); - settings.maxHeaderListSize(maxHeaderListSize); - - return new NettyClientHandler( - decoder, - encoder, - settings, - negotiationLogger, - lifecycleManager, - keepAliveManager, - stopwatchFactory, - tooManyPingsRunnable, - transportTracer, - eagAttributes, - authority, - autoFlowControl, - pingCounter); - } - - private NettyClientHandler( - Http2ConnectionDecoder decoder, - Http2ConnectionEncoder encoder, - Http2Settings settings, - ChannelLogger negotiationLogger, - ClientTransportLifecycleManager lifecycleManager, - KeepAliveManager keepAliveManager, - Supplier stopwatchFactory, - final Runnable tooManyPingsRunnable, - TransportTracer transportTracer, - Attributes eagAttributes, - String authority, - boolean autoFlowControl, - PingLimiter pingLimiter) { - super( - /* channelUnused= */ null, - decoder, - encoder, - settings, - negotiationLogger, - autoFlowControl, - pingLimiter); - this.lifecycleManager = lifecycleManager; - this.keepAliveManager = keepAliveManager; - this.stopwatchFactory = stopwatchFactory; - this.transportTracer = Preconditions.checkNotNull(transportTracer); - this.eagAttributes = eagAttributes; - this.authority = authority; - this.attributes = - Attributes.newBuilder().set(GrpcAttributes.ATTR_CLIENT_EAG_ATTRS, eagAttributes).build(); - - // Set the frame listener on the decoder. - decoder().frameListener(new FrameListener()); - - Http2Connection connection = encoder.connection(); - streamKey = connection.newKey(); - - connection.addListener( - new Http2ConnectionAdapter() { - @Override - public void onGoAwayReceived(int lastStreamId, long errorCode, ByteBuf debugData) { - byte[] debugDataBytes = ByteBufUtil.getBytes(debugData); - goingAway(errorCode, debugDataBytes); - if (errorCode == Http2Error.ENHANCE_YOUR_CALM.code()) { - String data = new String(debugDataBytes, UTF_8); - logger.log( - Level.WARNING, "Received GOAWAY with ENHANCE_YOUR_CALM. Debug data: {0}", data); - if ("too_many_pings".equals(data)) { - tooManyPingsRunnable.run(); - } - } - } - - @Override - public void onStreamActive(Http2Stream stream) { - if (connection().numActiveStreams() == 1 - && NettyClientHandler.this.keepAliveManager != null) { - NettyClientHandler.this.keepAliveManager.onTransportActive(); - } - } - - @Override - public void onStreamClosed(Http2Stream stream) { - // Although streams with CALL_OPTIONS_RPC_OWNED_BY_BALANCER are not marked as "in-use" - // in - // the first place, we don't propagate that option here, and it's safe to reset the - // in-use - // state for them, which will be a cheap no-op. - inUseState.updateObjectInUse(stream, false); - if (connection().numActiveStreams() == 0 - && NettyClientHandler.this.keepAliveManager != null) { - NettyClientHandler.this.keepAliveManager.onTransportIdle(); - } - } - }); - } - - /** - * The protocol negotiation attributes, available once the protocol negotiation completes; - * otherwise returns {@code Attributes.EMPTY}. - */ - Attributes getAttributes() { - return attributes; - } - - /** Handler for commands sent from the stream. */ - @Override - public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) - throws Exception { - if (msg instanceof CreateStreamCommand) { - createStream((CreateStreamCommand) msg, promise); - } else if (msg instanceof SendGrpcFrameCommand) { - sendGrpcFrame(ctx, (SendGrpcFrameCommand) msg, promise); - } else if (msg instanceof CancelClientStreamCommand) { - cancelStream(ctx, (CancelClientStreamCommand) msg, promise); - } else if (msg instanceof SendPingCommand) { - sendPingFrame(ctx, (SendPingCommand) msg, promise); - } else if (msg instanceof GracefulCloseCommand) { - gracefulClose(ctx, (GracefulCloseCommand) msg, promise); - } else if (msg instanceof ForcefulCloseCommand) { - forcefulClose(ctx, (ForcefulCloseCommand) msg, promise); - } else if (msg == NOOP_MESSAGE) { - ctx.write(Unpooled.EMPTY_BUFFER, promise); - } else { - throw new AssertionError("Write called for unexpected type: " + msg.getClass().getName()); - } - } - - void startWriteQueue(Channel channel) { - clientWriteQueue = new WriteQueue(channel); - } - - WriteQueue getWriteQueue() { - return clientWriteQueue; - } - - ClientTransportLifecycleManager getLifecycleManager() { - return lifecycleManager; - } - - /** Returns the given processed bytes back to inbound flow control. */ - void returnProcessedBytes(Http2Stream stream, int bytes) { - try { - decoder().flowController().consumeBytes(stream, bytes); - } catch (Http2Exception e) { - throw new RuntimeException(e); - } - } - - private void onHeadersRead(int streamId, Http2Headers headers, boolean endStream) { - // Stream 1 is reserved for the Upgrade response, so we should ignore its headers here: - if (streamId != Http2CodecUtil.HTTP_UPGRADE_STREAM_ID) { - NettyClientStream.TransportState stream = clientStream(requireHttp2Stream(streamId)); - PerfMark.event("NettyClientHandler.onHeadersRead", stream.tag()); - stream.transportHeadersReceived(headers, endStream); - } - - if (keepAliveManager != null) { - keepAliveManager.onDataReceived(); - } - } - - /** Handler for an inbound HTTP/2 DATA frame. */ - private void onDataRead(int streamId, ByteBuf data, int padding, boolean endOfStream) { - flowControlPing().onDataRead(data.readableBytes(), padding); - NettyClientStream.TransportState stream = clientStream(requireHttp2Stream(streamId)); - PerfMark.event("NettyClientHandler.onDataRead", stream.tag()); - stream.transportDataReceived(data, endOfStream); - if (keepAliveManager != null) { - keepAliveManager.onDataReceived(); - } - } - - /** Handler for an inbound HTTP/2 RST_STREAM frame, terminating a stream. */ - private void onRstStreamRead(int streamId, long errorCode) { - NettyClientStream.TransportState stream = clientStream(connection().stream(streamId)); - if (stream != null) { - PerfMark.event("NettyClientHandler.onRstStreamRead", stream.tag()); - Status status = statusFromH2Error(null, "RST_STREAM closed stream", errorCode, null); - stream.transportReportStatus( - status, - errorCode == Http2Error.REFUSED_STREAM.code() - ? RpcProgress.REFUSED - : RpcProgress.PROCESSED, - false /*stop delivery*/, - new Metadata()); - if (keepAliveManager != null) { - keepAliveManager.onDataReceived(); - } - } - } - - @Override - public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { - logger.fine("Network channel being closed by the application."); - if (ctx.channel().isActive()) { // Ignore notification that the socket was closed - lifecycleManager.notifyShutdown( - Status.UNAVAILABLE.withDescription("Transport closed for unknown reason")); - } - super.close(ctx, promise); - } - - /** Handler for the Channel shutting down. */ - @Override - public void channelInactive(ChannelHandlerContext ctx) throws Exception { - try { - logger.fine("Network channel is closed"); - Status status = Status.UNAVAILABLE.withDescription("Network closed for unknown reason"); - lifecycleManager.notifyShutdown(status); - final Status streamStatus; - if (channelInactiveReason != null) { - streamStatus = channelInactiveReason; - } else { - streamStatus = lifecycleManager.getShutdownStatus(); - } - try { - cancelPing(lifecycleManager.getShutdownThrowable()); - // Report status to the application layer for any open streams - connection() - .forEachActiveStream( - new Http2StreamVisitor() { - @Override - public boolean visit(Http2Stream stream) throws Http2Exception { - NettyClientStream.TransportState clientStream = clientStream(stream); - if (clientStream != null) { - clientStream.transportReportStatus(streamStatus, false, new Metadata()); - } - return true; - } - }); - } finally { - lifecycleManager.notifyTerminated(status); - } - } finally { - // Close any open streams - super.channelInactive(ctx); - if (keepAliveManager != null) { - keepAliveManager.onTransportTermination(); - } - } - } - - @Override - public void handleProtocolNegotiationCompleted( - Attributes attributes, InternalChannelz.Security securityInfo) { - this.attributes = this.attributes.toBuilder().setAll(attributes).build(); - this.securityInfo = securityInfo; - super.handleProtocolNegotiationCompleted(attributes, securityInfo); - writeBufferingAndRemove(ctx().channel()); - } - - static void writeBufferingAndRemove(Channel channel) { - checkNotNull(channel, "channel"); - ChannelHandlerContext handlerCtx = - channel.pipeline().context(WriteBufferingAndExceptionHandler.class); - if (handlerCtx == null) { - return; - } - ((WriteBufferingAndExceptionHandler) handlerCtx.handler()).writeBufferedAndRemove(handlerCtx); - } - - @Override - public Attributes getEagAttributes() { - return eagAttributes; - } - - @Override - public String getAuthority() { - return authority; - } - - InternalChannelz.Security getSecurityInfo() { - return securityInfo; - } - - @Override - protected void onConnectionError( - ChannelHandlerContext ctx, boolean outbound, Throwable cause, Http2Exception http2Ex) { - logger.log(Level.FINE, "Caught a connection error", cause); - lifecycleManager.notifyShutdown(Utils.statusFromThrowable(cause)); - // Parent class will shut down the Channel - super.onConnectionError(ctx, outbound, cause, http2Ex); - } - - @Override - protected void onStreamError( - ChannelHandlerContext ctx, - boolean outbound, - Throwable cause, - Http2Exception.StreamException http2Ex) { - // Close the stream with a status that contains the cause. - NettyClientStream.TransportState stream = clientStream(connection().stream(http2Ex.streamId())); - if (stream != null) { - stream.transportReportStatus(Utils.statusFromThrowable(cause), false, new Metadata()); - } else { - logger.log(Level.FINE, "Stream error for unknown stream " + http2Ex.streamId(), cause); - } - - // Delegate to the base class to send a RST_STREAM. - super.onStreamError(ctx, outbound, cause, http2Ex); - } - - @Override - protected boolean isGracefulShutdownComplete() { - // Only allow graceful shutdown to complete after all pending streams have completed. - return super.isGracefulShutdownComplete() - && ((StreamBufferingEncoder) encoder()).numBufferedStreams() == 0; - } - - /** - * Attempts to create a new stream from the given command. If there are too many active streams, - * the creation request is queued. - */ - private void createStream(CreateStreamCommand command, ChannelPromise promise) throws Exception { - if (lifecycleManager.getShutdownThrowable() != null) { - command.stream().setNonExistent(); - // The connection is going away (it is really the GOAWAY case), - // just terminate the stream now. - command - .stream() - .transportReportStatus( - lifecycleManager.getShutdownStatus(), RpcProgress.REFUSED, true, new Metadata()); - promise.setFailure(lifecycleManager.getShutdownThrowable()); - return; - } - - // Get the stream ID for the new stream. - int streamId; - try { - streamId = incrementAndGetNextStreamId(); - } catch (StatusException e) { - command.stream().setNonExistent(); - // Stream IDs have been exhausted for this connection. Fail the promise immediately. - promise.setFailure(e); - - // Initiate a graceful shutdown if we haven't already. - if (!connection().goAwaySent()) { - logger.fine( - "Stream IDs have been exhausted for this connection. " - + "Initiating graceful shutdown of the connection."); - lifecycleManager.notifyShutdown(e.getStatus()); - close(ctx(), ctx().newPromise()); - } - return; - } - if (connection().goAwayReceived()) { - Status s = abruptGoAwayStatus; - int maxActiveStreams = connection().local().maxActiveStreams(); - int lastStreamId = connection().local().lastStreamKnownByPeer(); - if (s == null) { - // Should be impossible, but handle pseudo-gracefully - s = - Status.INTERNAL.withDescription( - "Failed due to abrupt GOAWAY, but can't find GOAWAY details"); - } else if (streamId > lastStreamId) { - s = - s.augmentDescription( - "stream id: " + streamId + ", GOAWAY Last-Stream-ID:" + lastStreamId); - } else if (connection().local().numActiveStreams() == maxActiveStreams) { - s = s.augmentDescription("At MAX_CONCURRENT_STREAMS limit. limit: " + maxActiveStreams); - } - if (streamId > lastStreamId || connection().local().numActiveStreams() == maxActiveStreams) { - // This should only be reachable during onGoAwayReceived, as otherwise - // getShutdownThrowable() != null - command.stream().setNonExistent(); - command.stream().transportReportStatus(s, RpcProgress.REFUSED, true, new Metadata()); - promise.setFailure(s.asRuntimeException()); - return; - } - } - - NettyClientStream.TransportState stream = command.stream(); - Http2Headers headers = command.headers(); - stream.setId(streamId); - - PerfMark.startTask("NettyClientHandler.createStream", stream.tag()); - Histogram.Timer createStream = - perfmarkNettyClientHandlerDuration.labels("NettyClientHandler.createStream").startTimer(); - PerfMark.linkIn(command.getLink()); - try { - createStreamTraced( - streamId, stream, headers, command.isGet(), command.shouldBeCountedForInUse(), promise); - } finally { - PerfMark.stopTask("NettyClientHandler.createStream", stream.tag()); - createStream.observeDuration(); - } - } - - private void createStreamTraced( - final int streamId, - final NettyClientStream.TransportState stream, - final Http2Headers headers, - boolean isGet, - final boolean shouldBeCountedForInUse, - final ChannelPromise promise) { - // Create an intermediate promise so that we can intercept the failure reported back to the - // application. - Histogram.Timer createFutureTimer = createStreamCreateNewFuture.startTimer(); - ChannelPromise tempPromise = ctx().newPromise(); - createFutureTimer.observeDuration(); - - Histogram.Timer writeHeaderTimer = createStreamWriteHeaderDuration.startTimer(); - ChannelFuture future = encoder().writeHeaders(ctx(), streamId, headers, 0, isGet, tempPromise); - writeHeaderTimer.observeDuration(); - - Histogram.Timer addListenerTimer = createStreamAddListenerDuration.startTimer(); - future.addListener( - new ChannelFutureListener() { - @Override - public void operationComplete(ChannelFuture future) throws Exception { - if (future.isSuccess()) { - // The http2Stream will be null in case a stream buffered in the encoder - // was canceled via RST_STREAM. - Http2Stream http2Stream = connection().stream(streamId); - if (http2Stream != null) { - stream.getStatsTraceContext().clientOutboundHeaders(); - http2Stream.setProperty(streamKey, stream); - - // This delays the in-use state until the I/O completes, which technically may - // be later than we would like. - if (shouldBeCountedForInUse) { - inUseState.updateObjectInUse(http2Stream, true); - } - - // Attach the client stream to the HTTP/2 stream object as user data. - stream.setHttp2Stream(http2Stream); - } - // Otherwise, the stream has been cancelled and Netty is sending a - // RST_STREAM frame which causes it to purge pending writes from the - // flow-controller and delete the http2Stream. The stream listener has already - // been notified of cancellation so there is nothing to do. - - // Just forward on the success status to the original promise. - promise.setSuccess(); - } else { - final Throwable cause = future.cause(); - if (cause instanceof StreamBufferingEncoder.Http2GoAwayException) { - StreamBufferingEncoder.Http2GoAwayException e = - (StreamBufferingEncoder.Http2GoAwayException) cause; - Status status = - statusFromH2Error( - Status.Code.UNAVAILABLE, - "GOAWAY closed buffered stream", - e.errorCode(), - e.debugData()); - stream.transportReportStatus(status, RpcProgress.REFUSED, true, new Metadata()); - promise.setFailure(status.asRuntimeException()); - } else { - promise.setFailure(cause); - } - } - } - }); - addListenerTimer.observeDuration(); - } - - /** Cancels this stream. */ - private void cancelStream( - ChannelHandlerContext ctx, CancelClientStreamCommand cmd, ChannelPromise promise) { - NettyClientStream.TransportState stream = cmd.stream(); - PerfMark.startTask("NettyClientHandler.cancelStream", stream.tag()); - Histogram.Timer cancelStream = - perfmarkNettyClientHandlerDuration.labels("NettyClientHandler.cancelStream").startTimer(); - PerfMark.linkIn(cmd.getLink()); - try { - Status reason = cmd.reason(); - if (reason != null) { - stream.transportReportStatus(reason, true, new Metadata()); - } - if (!cmd.stream().isNonExistent()) { - encoder().writeRstStream(ctx, stream.id(), Http2Error.CANCEL.code(), promise); - } else { - promise.setSuccess(); - } - } finally { - PerfMark.stopTask("NettyClientHandler.cancelStream", stream.tag()); - cancelStream.observeDuration(); - } - } - - /** Sends the given GRPC frame for the stream. */ - private void sendGrpcFrame( - ChannelHandlerContext ctx, SendGrpcFrameCommand cmd, ChannelPromise promise) { - PerfMark.startTask("NettyClientHandler.sendGrpcFrame", cmd.stream().tag()); - Histogram.Timer sendGrpcFrame = - perfmarkNettyClientHandlerDuration.labels("NettyClientHandler.sendGrpcFrame").startTimer(); - PerfMark.linkIn(cmd.getLink()); - try { - // Call the base class to write the HTTP/2 DATA frame. - // Note: no need to flush since this is handled by the outbound flow controller. - encoder().writeData(ctx, cmd.stream().id(), cmd.content(), 0, cmd.endStream(), promise); - } finally { - PerfMark.stopTask("NettyClientHandler.sendGrpcFrame", cmd.stream().tag()); - sendGrpcFrame.observeDuration(); - } - } - - private void sendPingFrame( - ChannelHandlerContext ctx, SendPingCommand msg, ChannelPromise promise) { - PerfMark.startTask("NettyClientHandler.sendPingFrame"); - Histogram.Timer sendPingFrame = - perfmarkNettyClientHandlerDuration.labels("NettyClientHandler.sendPingFrame").startTimer(); - PerfMark.linkIn(msg.getLink()); - try { - sendPingFrameTraced(ctx, msg, promise); - } finally { - PerfMark.stopTask("NettyClientHandler.sendPingFrame"); - sendPingFrame.observeDuration(); - } - } - - /** - * Sends a PING frame. If a ping operation is already outstanding, the callback in the message is - * registered to be called when the existing operation completes, and no new frame is sent. - */ - private void sendPingFrameTraced( - ChannelHandlerContext ctx, SendPingCommand msg, ChannelPromise promise) { - // Don't check lifecycleManager.getShutdownStatus() since we want to allow pings after shutdown - // but before termination. After termination, messages will no longer arrive because the - // pipeline clears all handlers on channel close. - - PingCallback callback = msg.callback(); - Executor executor = msg.executor(); - // we only allow one outstanding ping at a time, so just add the callback to - // any outstanding operation - if (ping != null) { - promise.setSuccess(); - ping.addCallback(callback, executor); - return; - } - - // Use a new promise to prevent calling the callback twice on write failure: here and in - // NettyClientTransport.ping(). It may appear strange, but it will behave the same as if - // ping != null above. - promise.setSuccess(); - promise = ctx().newPromise(); - // set outstanding operation - long data = USER_PING_PAYLOAD; - Stopwatch stopwatch = stopwatchFactory.get(); - stopwatch.start(); - ping = new Http2Ping(data, stopwatch); - ping.addCallback(callback, executor); - // and then write the ping - encoder().writePing(ctx, false, USER_PING_PAYLOAD, promise); - ctx.flush(); - final Http2Ping finalPing = ping; - promise.addListener( - new ChannelFutureListener() { - @Override - public void operationComplete(ChannelFuture future) throws Exception { - if (future.isSuccess()) { - transportTracer.reportKeepAliveSent(); - } else { - Throwable cause = future.cause(); - if (cause instanceof ClosedChannelException) { - cause = lifecycleManager.getShutdownThrowable(); - if (cause == null) { - cause = - Status.UNKNOWN - .withDescription("Ping failed but for unknown reason.") - .withCause(future.cause()) - .asException(); - } - } - finalPing.failed(cause); - if (ping == finalPing) { - ping = null; - } - } - } - }); - } - - private void gracefulClose( - ChannelHandlerContext ctx, GracefulCloseCommand msg, ChannelPromise promise) - throws Exception { - lifecycleManager.notifyShutdown(msg.getStatus()); - // Explicitly flush to create any buffered streams before sending GOAWAY. - // TODO(ejona): determine if the need to flush is a bug in Netty - flush(ctx); - close(ctx, promise); - } - - private void forcefulClose( - final ChannelHandlerContext ctx, final ForcefulCloseCommand msg, ChannelPromise promise) - throws Exception { - connection() - .forEachActiveStream( - new Http2StreamVisitor() { - @Override - public boolean visit(Http2Stream stream) throws Http2Exception { - NettyClientStream.TransportState clientStream = clientStream(stream); - Tag tag = clientStream != null ? clientStream.tag() : PerfMark.createTag(); - PerfMark.startTask("NettyClientHandler.forcefulClose", tag); - Histogram.Timer forcefulClose = - perfmarkNettyClientHandlerDuration - .labels("NettyClientHandler.forcefulClose") - .startTimer(); - PerfMark.linkIn(msg.getLink()); - try { - if (clientStream != null) { - clientStream.transportReportStatus(msg.getStatus(), true, new Metadata()); - resetStream(ctx, stream.id(), Http2Error.CANCEL.code(), ctx.newPromise()); - } - stream.close(); - return true; - } finally { - PerfMark.stopTask("NettyClientHandler.forcefulClose", tag); - forcefulClose.observeDuration(); - } - } - }); - close(ctx, promise); - } - - /** - * Handler for a GOAWAY being received. Fails any streams created after the last known stream. May - * only be called during a read. - */ - private void goingAway(long errorCode, byte[] debugData) { - Status finalStatus = - statusFromH2Error( - Status.Code.UNAVAILABLE, "GOAWAY shut down transport", errorCode, debugData); - lifecycleManager.notifyGracefulShutdown(finalStatus); - abruptGoAwayStatus = - statusFromH2Error( - Status.Code.UNAVAILABLE, "Abrupt GOAWAY closed unsent stream", errorCode, debugData); - // While this _should_ be UNAVAILABLE, Netty uses the wrong stream id in the GOAWAY when it - // fails streams due to HPACK failures (e.g., header list too large). To be more conservative, - // we assume any sent streams may be related to the GOAWAY. This should rarely impact users - // since the main time servers should use abrupt GOAWAYs is if there is a protocol error, and if - // there wasn't a protocol error the error code was probably NO_ERROR which is mapped to - // UNAVAILABLE. https://github.com/netty/netty/issues/10670 - final Status abruptGoAwayStatusConservative = - statusFromH2Error(null, "Abrupt GOAWAY closed sent stream", errorCode, debugData); - // Try to allocate as many in-flight streams as possible, to reduce race window of - // https://github.com/grpc/grpc-java/issues/2562 . To be of any help, the server has to - // gracefully shut down the connection with two GOAWAYs. gRPC servers generally send a PING - // after the first GOAWAY, so they can very precisely detect when the GOAWAY has been - // processed and thus this processing must be in-line before processing additional reads. - - // This can cause reentrancy, but should be minor since it is normal to handle writes in - // response to a read. Also, the call stack is rather shallow at this point - clientWriteQueue.drainNow(); - if (lifecycleManager.notifyShutdown(finalStatus)) { - // This is for the only RPCs that are actually covered by the GOAWAY error code. All other - // RPCs were not observed by the remote and so should be UNAVAILABLE. - channelInactiveReason = - statusFromH2Error(null, "Connection closed after GOAWAY", errorCode, debugData); - } - - final int lastKnownStream = connection().local().lastStreamKnownByPeer(); - try { - connection() - .forEachActiveStream( - new Http2StreamVisitor() { - @Override - public boolean visit(Http2Stream stream) throws Http2Exception { - if (stream.id() > lastKnownStream) { - NettyClientStream.TransportState clientStream = clientStream(stream); - if (clientStream != null) { - // RpcProgress _should_ be REFUSED, but are being conservative. See comment - // for - // abruptGoAwayStatusConservative. This does reduce our ability to perform - // transparent - // retries, but our main goal of transporent retries is to resolve the local - // race. We - // still hope/expect servers to use the graceful double-GOAWAY when closing - // connections. - clientStream.transportReportStatus( - abruptGoAwayStatusConservative, - RpcProgress.PROCESSED, - false, - new Metadata()); - } - stream.close(); - } - return true; - } - }); - } catch (Http2Exception e) { - throw new RuntimeException(e); - } - } - - private void cancelPing(Throwable t) { - if (ping != null) { - ping.failed(t); - ping = null; - } - } - - /** If {@code statusCode} is non-null, it will be used instead of the http2 error code mapping. */ - private Status statusFromH2Error( - Status.Code statusCode, String context, long errorCode, byte[] debugData) { - Status status = GrpcUtil.Http2Error.statusForCode((int) errorCode); - if (statusCode == null) { - statusCode = status.getCode(); - } - String debugString = ""; - if (debugData != null && debugData.length > 0) { - // If a debug message was provided, use it. - debugString = ", debug data: " + new String(debugData, UTF_8); - } - return statusCode - .toStatus() - .withDescription(context + ". " + status.getDescription() + debugString); - } - - /** Gets the client stream associated to the given HTTP/2 stream object. */ - private NettyClientStream.TransportState clientStream(Http2Stream stream) { - return stream == null ? null : (NettyClientStream.TransportState) stream.getProperty(streamKey); - } - - private int incrementAndGetNextStreamId() throws StatusException { - int nextStreamId = connection().local().incrementAndGetNextStreamId(); - if (nextStreamId < 0) { - logger.fine( - "Stream IDs have been exhausted for this connection. " - + "Initiating graceful shutdown of the connection."); - throw EXHAUSTED_STREAMS_STATUS.asException(); - } - return nextStreamId; - } - - private Http2Stream requireHttp2Stream(int streamId) { - Http2Stream stream = connection().stream(streamId); - if (stream == null) { - // This should never happen. - throw new AssertionError("Stream does not exist: " + streamId); - } - return stream; - } - - private class FrameListener extends Http2FrameAdapter { - private boolean firstSettings = true; - - @Override - public void onSettingsRead(ChannelHandlerContext ctx, Http2Settings settings) { - if (firstSettings) { - firstSettings = false; - lifecycleManager.notifyReady(); - } - } - - @Override - public int onDataRead( - ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, boolean endOfStream) - throws Http2Exception { - NettyClientHandler.this.onDataRead(streamId, data, padding, endOfStream); - return padding; - } - - @Override - public void onHeadersRead( - ChannelHandlerContext ctx, - int streamId, - Http2Headers headers, - int streamDependency, - short weight, - boolean exclusive, - int padding, - boolean endStream) - throws Http2Exception { - NettyClientHandler.this.onHeadersRead(streamId, headers, endStream); - } - - @Override - public void onRstStreamRead(ChannelHandlerContext ctx, int streamId, long errorCode) - throws Http2Exception { - NettyClientHandler.this.onRstStreamRead(streamId, errorCode); - } - - @Override - public void onPingAckRead(ChannelHandlerContext ctx, long ackPayload) throws Http2Exception { - Http2Ping p = ping; - if (ackPayload == flowControlPing().payload()) { - flowControlPing().updateWindow(); - if (logger.isLoggable(Level.FINE)) { - logger.log( - Level.FINE, - String.format( - "Window: %d", - decoder().flowController().initialWindowSize(connection().connectionStream()))); - } - } else if (p != null) { - if (p.payload() == ackPayload) { - p.complete(); - ping = null; - } else { - logger.log( - Level.WARNING, - String.format( - "Received unexpected ping ack. Expecting %d, got %d", p.payload(), ackPayload)); - } - } else { - logger.warning("Received unexpected ping ack. No ping outstanding"); - } - if (keepAliveManager != null) { - keepAliveManager.onDataReceived(); - } - } - - @Override - public void onPingRead(ChannelHandlerContext ctx, long data) throws Http2Exception { - if (keepAliveManager != null) { - keepAliveManager.onDataReceived(); - } - } - } - - private static class PingCountingFrameWriter extends DecoratingHttp2FrameWriter - implements AbstractNettyHandler.PingLimiter { - private int pingCount; - - public PingCountingFrameWriter(Http2FrameWriter delegate) { - super(delegate); - } - - @Override - public boolean isPingAllowed() { - // "3 strikes" may cause the server to complain, so we limit ourselves to 2 or below. - return pingCount < 2; - } - - @Override - public ChannelFuture writeHeaders( - ChannelHandlerContext ctx, - int streamId, - Http2Headers headers, - int padding, - boolean endStream, - ChannelPromise promise) { - pingCount = 0; - return super.writeHeaders(ctx, streamId, headers, padding, endStream, promise); - } - - @Override - public ChannelFuture writeHeaders( - ChannelHandlerContext ctx, - int streamId, - Http2Headers headers, - int streamDependency, - short weight, - boolean exclusive, - int padding, - boolean endStream, - ChannelPromise promise) { - pingCount = 0; - return super.writeHeaders( - ctx, streamId, headers, streamDependency, weight, exclusive, padding, endStream, promise); - } - - @Override - public ChannelFuture writeWindowUpdate( - ChannelHandlerContext ctx, int streamId, int windowSizeIncrement, ChannelPromise promise) { - pingCount = 0; - return super.writeWindowUpdate(ctx, streamId, windowSizeIncrement, promise); - } - - @Override - public ChannelFuture writePing( - ChannelHandlerContext ctx, boolean ack, long data, ChannelPromise promise) { - if (!ack) { - pingCount++; - } - return super.writePing(ctx, ack, data, promise); - } - - @Override - public ChannelFuture writeData( - ChannelHandlerContext ctx, - int streamId, - ByteBuf data, - int padding, - boolean endStream, - ChannelPromise promise) { - if (data.isReadable()) { - pingCount = 0; - } - return super.writeData(ctx, streamId, data, padding, endStream, promise); - } - } -} diff --git a/src/main/java/io/grpc/netty/NettyClientStream.java b/src/main/java/io/grpc/netty/NettyClientStream.java deleted file mode 100644 index e211274599f..00000000000 --- a/src/main/java/io/grpc/netty/NettyClientStream.java +++ /dev/null @@ -1,375 +0,0 @@ -/* - * Copyright 2015 The gRPC Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * 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. - */ - -package io.grpc.netty; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; -import static io.netty.buffer.Unpooled.EMPTY_BUFFER; - -import com.google.common.base.Preconditions; -import com.google.common.io.BaseEncoding; -import io.grpc.Attributes; -import io.grpc.CallOptions; -import io.grpc.InternalKnownTransport; -import io.grpc.InternalMethodDescriptor; -import io.grpc.Metadata; -import io.grpc.MethodDescriptor; -import io.grpc.Status; -import io.grpc.internal.AbstractClientStream; -import io.grpc.internal.Http2ClientStreamTransportState; -import io.grpc.internal.StatsTraceContext; -import io.grpc.internal.TransportTracer; -import io.grpc.internal.WritableBuffer; -import io.netty.buffer.ByteBuf; -import io.netty.channel.Channel; -import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelFutureListener; -import io.netty.channel.EventLoop; -import io.netty.handler.codec.http2.Http2Headers; -import io.netty.handler.codec.http2.Http2Stream; -import io.netty.util.AsciiString; -import io.perfmark.PerfMark; -import io.perfmark.Tag; -import io.prometheus.client.Histogram; -import javax.annotation.Nullable; -import org.tikv.common.util.HistogramUtils; - -/** Client stream for a Netty transport. Must only be called from the sending application thread. */ -class NettyClientStream extends AbstractClientStream { - private static final InternalMethodDescriptor methodDescriptorAccessor = - new InternalMethodDescriptor( - NettyClientTransport.class.getName().contains("grpc.netty.shaded") - ? InternalKnownTransport.NETTY_SHADED - : InternalKnownTransport.NETTY); - - private final Sink sink = new Sink(); - private final TransportState state; - private final WriteQueue writeQueue; - private final MethodDescriptor method; - private AsciiString authority; - private final AsciiString scheme; - private final AsciiString userAgent; - - public static final Histogram perfmarkNettyClientStreamDuration = - HistogramUtils.buildDuration() - .name("perfmark_netty_client_stream_duration_seconds") - .help("Perfmark netty client stream duration seconds") - .labelNames("type") - .register(); - - NettyClientStream( - TransportState state, - MethodDescriptor method, - Metadata headers, - Channel channel, - AsciiString authority, - AsciiString scheme, - AsciiString userAgent, - StatsTraceContext statsTraceCtx, - TransportTracer transportTracer, - CallOptions callOptions, - boolean useGetForSafeMethods) { - super( - new NettyWritableBufferAllocator(channel.alloc()), - statsTraceCtx, - transportTracer, - headers, - callOptions, - useGetForSafeMethods && method.isSafe()); - this.state = checkNotNull(state, "transportState"); - this.writeQueue = state.handler.getWriteQueue(); - this.method = checkNotNull(method, "method"); - this.authority = checkNotNull(authority, "authority"); - this.scheme = checkNotNull(scheme, "scheme"); - this.userAgent = userAgent; - } - - @Override - protected TransportState transportState() { - return state; - } - - @Override - protected Sink abstractClientStreamSink() { - return sink; - } - - @Override - public void setAuthority(String authority) { - this.authority = AsciiString.of(checkNotNull(authority, "authority")); - } - - @Override - public Attributes getAttributes() { - return state.handler.getAttributes(); - } - - private class Sink implements AbstractClientStream.Sink { - - @Override - public void writeHeaders(Metadata headers, byte[] requestPayload) { - PerfMark.startTask("NettyClientStream$Sink.writeHeaders"); - Histogram.Timer writeHeaders = - perfmarkNettyClientStreamDuration - .labels("NettyClientStream$Sink.writeHeaders") - .startTimer(); - try { - writeHeadersInternal(headers, requestPayload); - } finally { - PerfMark.stopTask("NettyClientStream$Sink.writeHeaders"); - writeHeaders.observeDuration(); - } - } - - private void writeHeadersInternal(Metadata headers, byte[] requestPayload) { - // Convert the headers into Netty HTTP/2 headers. - AsciiString defaultPath = (AsciiString) methodDescriptorAccessor.geRawMethodName(method); - if (defaultPath == null) { - defaultPath = new AsciiString("/" + method.getFullMethodName()); - methodDescriptorAccessor.setRawMethodName(method, defaultPath); - } - boolean get = (requestPayload != null); - AsciiString httpMethod; - if (get) { - // Forge the query string - // TODO(ericgribkoff) Add the key back to the query string - defaultPath = - new AsciiString(defaultPath + "?" + BaseEncoding.base64().encode(requestPayload)); - httpMethod = Utils.HTTP_GET_METHOD; - } else { - httpMethod = Utils.HTTP_METHOD; - } - Http2Headers http2Headers = - Utils.convertClientHeaders( - headers, scheme, defaultPath, authority, httpMethod, userAgent); - - ChannelFutureListener failureListener = - new ChannelFutureListener() { - @Override - public void operationComplete(ChannelFuture future) throws Exception { - if (!future.isSuccess()) { - // Stream creation failed. Close the stream if not already closed. - // When the channel is shutdown, the lifecycle manager has a better view of the - // failure, - // especially before negotiation completes (because the negotiator commonly doesn't - // receive the execeptionCaught because NettyClientHandler does not propagate it). - Status s = transportState().handler.getLifecycleManager().getShutdownStatus(); - if (s == null) { - s = transportState().statusFromFailedFuture(future); - } - transportState().transportReportStatus(s, true, new Metadata()); - } - } - }; - // Write the command requesting the creation of the stream. - writeQueue - .enqueue( - new CreateStreamCommand( - http2Headers, transportState(), shouldBeCountedForInUse(), get), - !method.getType().clientSendsOneMessage() || get) - .addListener(failureListener); - } - - private void writeFrameInternal( - WritableBuffer frame, boolean endOfStream, boolean flush, final int numMessages) { - Preconditions.checkArgument(numMessages >= 0); - ByteBuf bytebuf = - frame == null ? EMPTY_BUFFER : ((NettyWritableBuffer) frame).bytebuf().touch(); - final int numBytes = bytebuf.readableBytes(); - if (numBytes > 0) { - // Add the bytes to outbound flow control. - onSendingBytes(numBytes); - writeQueue - .enqueue(new SendGrpcFrameCommand(transportState(), bytebuf, endOfStream), flush) - .addListener( - new ChannelFutureListener() { - @Override - public void operationComplete(ChannelFuture future) throws Exception { - // If the future succeeds when http2stream is null, the stream has been - // cancelled - // before it began and Netty is purging pending writes from the flow-controller. - if (future.isSuccess() && transportState().http2Stream() != null) { - // Remove the bytes from outbound flow control, optionally notifying - // the client that they can send more bytes. - transportState().onSentBytes(numBytes); - NettyClientStream.this.getTransportTracer().reportMessageSent(numMessages); - } - } - }); - } else { - // The frame is empty and will not impact outbound flow control. Just send it. - writeQueue.enqueue(new SendGrpcFrameCommand(transportState(), bytebuf, endOfStream), flush); - } - } - - @Override - public void writeFrame( - WritableBuffer frame, boolean endOfStream, boolean flush, int numMessages) { - PerfMark.startTask("NettyClientStream$Sink.writeFrame"); - Histogram.Timer writeFrame = - perfmarkNettyClientStreamDuration - .labels("NettyClientStream$Sink.writeFrame") - .startTimer(); - try { - writeFrameInternal(frame, endOfStream, flush, numMessages); - } finally { - PerfMark.stopTask("NettyClientStream$Sink.writeFrame"); - writeFrame.observeDuration(); - } - } - - @Override - public void cancel(Status status) { - PerfMark.startTask("NettyClientStream$Sink.cancel"); - Histogram.Timer cancel = - perfmarkNettyClientStreamDuration.labels("NettyClientStream$Sink.cancel").startTimer(); - try { - writeQueue.enqueue(new CancelClientStreamCommand(transportState(), status), true); - } finally { - PerfMark.stopTask("NettyClientStream$Sink.cancel"); - cancel.observeDuration(); - } - } - } - - /** This should only called from the transport thread. */ - public abstract static class TransportState extends Http2ClientStreamTransportState - implements StreamIdHolder { - private static final int NON_EXISTENT_ID = -1; - - private final String methodName; - private final NettyClientHandler handler; - private final EventLoop eventLoop; - private int id; - private Http2Stream http2Stream; - private Tag tag; - - protected TransportState( - NettyClientHandler handler, - EventLoop eventLoop, - int maxMessageSize, - StatsTraceContext statsTraceCtx, - TransportTracer transportTracer, - String methodName) { - super(maxMessageSize, statsTraceCtx, transportTracer); - this.methodName = checkNotNull(methodName, "methodName"); - this.handler = checkNotNull(handler, "handler"); - this.eventLoop = checkNotNull(eventLoop, "eventLoop"); - tag = PerfMark.createTag(methodName); - } - - @Override - public int id() { - // id should be positive - return id; - } - - public void setId(int id) { - checkArgument(id > 0, "id must be positive %s", id); - checkState(this.id == 0, "id has been previously set: %s", this.id); - this.id = id; - this.tag = PerfMark.createTag(methodName, id); - } - - /** - * Marks the stream state as if it had never existed. This can happen if the stream is cancelled - * after it is created, but before it has been started. - */ - void setNonExistent() { - checkState(this.id == 0, "Id has been previously set: %s", this.id); - this.id = NON_EXISTENT_ID; - } - - boolean isNonExistent() { - return this.id == NON_EXISTENT_ID; - } - - /** - * Sets the underlying Netty {@link Http2Stream} for this stream. This must be called in the - * context of the transport thread. - */ - public void setHttp2Stream(Http2Stream http2Stream) { - checkNotNull(http2Stream, "http2Stream"); - checkState(this.http2Stream == null, "Can only set http2Stream once"); - this.http2Stream = http2Stream; - - // Now that the stream has actually been initialized, call the listener's onReady callback if - // appropriate. - onStreamAllocated(); - getTransportTracer().reportLocalStreamStarted(); - } - - /** Gets the underlying Netty {@link Http2Stream} for this stream. */ - @Nullable - public Http2Stream http2Stream() { - return http2Stream; - } - - /** - * Intended to be overridden by NettyClientTransport, which has more information about failures. - * May only be called from event loop. - */ - protected abstract Status statusFromFailedFuture(ChannelFuture f); - - @Override - protected void http2ProcessingFailed(Status status, boolean stopDelivery, Metadata trailers) { - transportReportStatus(status, stopDelivery, trailers); - handler.getWriteQueue().enqueue(new CancelClientStreamCommand(this, status), true); - } - - @Override - public void runOnTransportThread(final Runnable r) { - if (eventLoop.inEventLoop()) { - r.run(); - } else { - eventLoop.execute(r); - } - } - - @Override - public void bytesRead(int processedBytes) { - handler.returnProcessedBytes(http2Stream, processedBytes); - handler.getWriteQueue().scheduleFlush(); - } - - @Override - public void deframeFailed(Throwable cause) { - http2ProcessingFailed(Status.fromThrowable(cause), true, new Metadata()); - } - - void transportHeadersReceived(Http2Headers headers, boolean endOfStream) { - if (endOfStream) { - if (!isOutboundClosed()) { - handler.getWriteQueue().enqueue(new CancelClientStreamCommand(this, null), true); - } - transportTrailersReceived(Utils.convertTrailers(headers)); - } else { - transportHeadersReceived(Utils.convertHeaders(headers)); - } - } - - void transportDataReceived(ByteBuf frame, boolean endOfStream) { - transportDataReceived(new NettyReadableBuffer(frame.retain()), endOfStream); - } - - @Override - public final Tag tag() { - return tag; - } - } -} diff --git a/src/main/java/io/grpc/netty/WriteQueue.java b/src/main/java/io/grpc/netty/WriteQueue.java deleted file mode 100644 index 203ac42c43c..00000000000 --- a/src/main/java/io/grpc/netty/WriteQueue.java +++ /dev/null @@ -1,316 +0,0 @@ -/* - * Copyright 2015 The gRPC Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * 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. - */ - -package io.grpc.netty; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Preconditions; -import com.google.errorprone.annotations.CanIgnoreReturnValue; -import io.netty.channel.Channel; -import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelPromise; -import io.perfmark.Link; -import io.perfmark.PerfMark; -import io.prometheus.client.Histogram; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.atomic.AtomicBoolean; -import org.apache.commons.lang3.tuple.Pair; -import org.tikv.common.util.HistogramUtils; - -/** A queue of pending writes to a {@link Channel} that is flushed as a single unit. */ -class WriteQueue { - - // Dequeue in chunks, so we don't have to acquire the queue's log too often. - @VisibleForTesting static final int DEQUE_CHUNK_SIZE = 128; - - /** {@link Runnable} used to schedule work onto the tail of the event loop. */ - private final Runnable later = - new Runnable() { - @Override - public void run() { - flush(); - } - }; - - private final Channel channel; - private final Queue> queue; - private final AtomicBoolean scheduled = new AtomicBoolean(); - - public static final Histogram writeQueuePendingDuration = - HistogramUtils.buildDuration() - .name("grpc_netty_write_queue_pending_duration_ms") - .labelNames("type") - .help("Pending duration of a task in the write queue.") - .register(); - - public static final Histogram writeQueueWaitBatchDuration = - HistogramUtils.buildDuration() - .name("grpc_netty_write_queue_wait_batch_duration_seconds") - .help("Duration of waiting a batch filled in the write queue.") - .register(); - - public static final Histogram writeQueueBatchSize = - Histogram.build() - .exponentialBuckets(1, 2, 10) - .name("grpc_netty_write_queue_batch_size") - .help("Number of tasks in a batch in the write queue.") - .register(); - - public static final Histogram writeQueueCmdRunDuration = - HistogramUtils.buildDuration() - .name("grpc_netty_write_queue_cmd_run_duration_seconds") - .help("Duration of a task execution in the write queue.") - .labelNames("type") - .register(); - - public static final Histogram writeQueueChannelFlushDuration = - HistogramUtils.buildDuration() - .name("grpc_netty_write_queue_channel_flush_duration_seconds") - .help("Duration of a channel flush in the write queue.") - .labelNames("phase") - .register(); - - public static final Histogram writeQueueFlushDuration = - HistogramUtils.buildDuration() - .name("grpc_netty_write_queue_flush_duration_seconds") - .help("Duration of a flush of the write queue.") - .register(); - - public static final Histogram perfmarkWriteQueueDuration = - HistogramUtils.buildDuration() - .name("perfmark_write_queue_duration_seconds") - .help("Perfmark write queue duration seconds") - .labelNames("type") - .register(); - - public WriteQueue(Channel channel) { - this.channel = Preconditions.checkNotNull(channel, "channel"); - queue = new ConcurrentLinkedQueue<>(); - } - - /** Schedule a flush on the channel. */ - void scheduleFlush() { - if (scheduled.compareAndSet(false, true)) { - // Add the queue to the tail of the event loop so writes will be executed immediately - // inside the event loop. Note DO NOT do channel.write outside the event loop as - // it will not wake up immediately without a flush. - channel.eventLoop().execute(later); - } - } - - /** - * Enqueue a write command on the channel. - * - * @param command a write to be executed on the channel. - * @param flush true if a flush of the write should be schedule, false if a later call to enqueue - * will schedule the flush. - */ - @CanIgnoreReturnValue - ChannelFuture enqueue(QueuedCommand command, boolean flush) { - // Detect erroneous code that tries to reuse command objects. - Preconditions.checkArgument(command.promise() == null, "promise must not be set on command"); - - ChannelPromise promise = channel.newPromise(); - command.promise(promise); - queue.add(Pair.of(command, System.nanoTime())); - if (flush) { - scheduleFlush(); - } - return promise; - } - - /** - * Enqueue the runnable. It is not safe for another thread to queue an Runnable directly to the - * event loop, because it will be out-of-order with writes. This method allows the Runnable to be - * processed in-order with writes. - */ - void enqueue(Runnable runnable, boolean flush) { - Long now = System.nanoTime(); - queue.add(Pair.of(new RunnableCommand(runnable), now)); - if (flush) { - scheduleFlush(); - } - } - - /** - * Executes enqueued work directly on the current thread. This can be used to trigger writes - * before performing additional reads. Must be called from the event loop. This method makes no - * guarantee that the work queue is empty when it returns. - */ - void drainNow() { - Preconditions.checkState(channel.eventLoop().inEventLoop(), "must be on the event loop"); - if (queue.peek() == null) { - return; - } - flush(); - } - - /** - * Process the queue of commands and dispatch them to the stream. This method is only called in - * the event loop - */ - private void flush() { - Histogram.Timer flushTimer = writeQueueFlushDuration.startTimer(); - PerfMark.startTask("WriteQueue.periodicFlush"); - Histogram.Timer periodicFlush = - perfmarkWriteQueueDuration.labels("WriteQueue.periodicFlush").startTimer(); - - long start = System.nanoTime(); - try { - Pair item; - int i = 0; - boolean flushedOnce = false; - Histogram.Timer waitBatchTimer = writeQueueWaitBatchDuration.startTimer(); - while ((item = queue.poll()) != null) { - QueuedCommand cmd = item.getLeft(); - String cmdName = cmd.getClass().getSimpleName(); - writeQueuePendingDuration - .labels(cmdName) - .observe((System.nanoTime() - item.getRight()) / 1_000_000.0); - - Histogram.Timer cmdTimer = writeQueueCmdRunDuration.labels(cmdName).startTimer(); - - // Run the command - cmd.run(channel); - - cmdTimer.observeDuration(); - - if (++i == DEQUE_CHUNK_SIZE) { - waitBatchTimer.observeDuration(); - i = 0; - // Flush each chunk so we are releasing buffers periodically. In theory this loop - // might never end as new events are continuously added to the queue, if we never - // flushed in that case we would be guaranteed to OOM. - PerfMark.startTask("WriteQueue.flush0"); - Histogram.Timer flush0 = - perfmarkWriteQueueDuration.labels("WriteQueue.flush0").startTimer(); - Histogram.Timer channelFlushTimer = - writeQueueChannelFlushDuration.labels("flush0").startTimer(); - try { - channel.flush(); - } finally { - waitBatchTimer = writeQueueWaitBatchDuration.startTimer(); - writeQueueBatchSize.observe(DEQUE_CHUNK_SIZE); - channelFlushTimer.observeDuration(); - PerfMark.stopTask("WriteQueue.flush0"); - flush0.observeDuration(); - } - flushedOnce = true; - } - } - // Must flush at least once, even if there were no writes. - if (i != 0 || !flushedOnce) { - waitBatchTimer.observeDuration(); - PerfMark.startTask("WriteQueue.flush1"); - Histogram.Timer flush1 = - perfmarkWriteQueueDuration.labels("WriteQueue.flush1").startTimer(); - Histogram.Timer channelFlushTimer = - writeQueueChannelFlushDuration.labels("flush1").startTimer(); - try { - channel.flush(); - } finally { - writeQueueBatchSize.observe(i); - channelFlushTimer.observeDuration(); - PerfMark.stopTask("WriteQueue.flush1"); - flush1.observeDuration(); - } - } - } finally { - PerfMark.stopTask("WriteQueue.periodicFlush"); - periodicFlush.observeDuration(); - flushTimer.observeDuration(); - // Mark the write as done, if the queue is non-empty after marking trigger a new write. - scheduled.set(false); - if (!queue.isEmpty()) { - scheduleFlush(); - } - } - } - - private static class RunnableCommand implements QueuedCommand { - private final Runnable runnable; - private final Link link; - - public RunnableCommand(Runnable runnable) { - this.link = PerfMark.linkOut(); - this.runnable = runnable; - } - - @Override - public final void promise(ChannelPromise promise) { - throw new UnsupportedOperationException(); - } - - @Override - public final ChannelPromise promise() { - throw new UnsupportedOperationException(); - } - - @Override - public final void run(Channel channel) { - runnable.run(); - } - - @Override - public Link getLink() { - return link; - } - } - - abstract static class AbstractQueuedCommand implements QueuedCommand { - - private ChannelPromise promise; - private final Link link; - - AbstractQueuedCommand() { - this.link = PerfMark.linkOut(); - } - - @Override - public final void promise(ChannelPromise promise) { - this.promise = promise; - } - - @Override - public final ChannelPromise promise() { - return promise; - } - - @Override - public final void run(Channel channel) { - channel.write(this, promise); - } - - @Override - public Link getLink() { - return link; - } - } - - /** Simple wrapper type around a command and its optional completion listener. */ - interface QueuedCommand { - /** Returns the promise beeing notified of the success/failure of the write. */ - ChannelPromise promise(); - - /** Sets the promise. */ - void promise(ChannelPromise promise); - - void run(Channel channel); - - Link getLink(); - } -} diff --git a/src/main/java/io/grpc/stub/ClientCalls.java b/src/main/java/io/grpc/stub/ClientCalls.java deleted file mode 100644 index 818ebfabe6f..00000000000 --- a/src/main/java/io/grpc/stub/ClientCalls.java +++ /dev/null @@ -1,805 +0,0 @@ -/* - * Copyright 2014 The gRPC Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * 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. - */ - -package io.grpc.stub; - -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; - -import com.google.common.base.MoreObjects; -import com.google.common.base.Preconditions; -import com.google.common.util.concurrent.AbstractFuture; -import com.google.common.util.concurrent.ListenableFuture; -import io.grpc.CallOptions; -import io.grpc.Channel; -import io.grpc.ClientCall; -import io.grpc.Metadata; -import io.grpc.MethodDescriptor; -import io.grpc.Status; -import io.grpc.StatusException; -import io.grpc.StatusRuntimeException; -import io.prometheus.client.Histogram; -import java.util.Iterator; -import java.util.NoSuchElementException; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executor; -import java.util.concurrent.Future; -import java.util.concurrent.locks.LockSupport; -import java.util.logging.Level; -import java.util.logging.Logger; -import javax.annotation.Nullable; -import org.tikv.common.util.HistogramUtils; - -/** - * Utility functions for processing different call idioms. We have one-to-one correspondence between - * utilities in this class and the potential signatures in a generated stub class so that the - * runtime can vary behavior without requiring regeneration of the stub. - */ -public final class ClientCalls { - - private static final Logger logger = Logger.getLogger(ClientCalls.class.getName()); - - public static final Histogram asyncUnaryRequestCallDuration = - HistogramUtils.buildDuration() - .name("grpc_client_async_unary_request_call_duration_seconds") - .help("Histogram of time spent in asyncUnaryRequestCall") - .labelNames("phase") - .register(); - - public static final Histogram blockingUnaryRequestWaitDuration = - HistogramUtils.buildDuration() - .name("grpc_client_blocking_unary_request_wait_duration_seconds") - .help("Histogram of time spent waiting for future in blockingUnaryCall") - .register(); - - // Prevent instantiation - private ClientCalls() {} - - /** - * Executes a unary call with a response {@link StreamObserver}. The {@code call} should not be - * already started. After calling this method, {@code call} should no longer be used. - * - *

If the provided {@code responseObserver} is an instance of {@link ClientResponseObserver}, - * {@code beforeStart()} will be called. - */ - public static void asyncUnaryCall( - ClientCall call, ReqT req, StreamObserver responseObserver) { - asyncUnaryRequestCall(call, req, responseObserver, false); - } - - /** - * Executes a server-streaming call with a response {@link StreamObserver}. The {@code call} - * should not be already started. After calling this method, {@code call} should no longer be - * used. - * - *

If the provided {@code responseObserver} is an instance of {@link ClientResponseObserver}, - * {@code beforeStart()} will be called. - */ - public static void asyncServerStreamingCall( - ClientCall call, ReqT req, StreamObserver responseObserver) { - asyncUnaryRequestCall(call, req, responseObserver, true); - } - - /** - * Executes a client-streaming call returning a {@link StreamObserver} for the request messages. - * The {@code call} should not be already started. After calling this method, {@code call} should - * no longer be used. - * - *

If the provided {@code responseObserver} is an instance of {@link ClientResponseObserver}, - * {@code beforeStart()} will be called. - * - * @return request stream observer. It will extend {@link ClientCallStreamObserver} - */ - public static StreamObserver asyncClientStreamingCall( - ClientCall call, StreamObserver responseObserver) { - return asyncStreamingRequestCall(call, responseObserver, false); - } - - /** - * Executes a bidirectional-streaming call. The {@code call} should not be already started. After - * calling this method, {@code call} should no longer be used. - * - *

If the provided {@code responseObserver} is an instance of {@link ClientResponseObserver}, - * {@code beforeStart()} will be called. - * - * @return request stream observer. It will extend {@link ClientCallStreamObserver} - */ - public static StreamObserver asyncBidiStreamingCall( - ClientCall call, StreamObserver responseObserver) { - return asyncStreamingRequestCall(call, responseObserver, true); - } - - /** - * Executes a unary call and blocks on the response. The {@code call} should not be already - * started. After calling this method, {@code call} should no longer be used. - * - * @return the single response message. - * @throws StatusRuntimeException on error - */ - public static RespT blockingUnaryCall(ClientCall call, ReqT req) { - try { - return getUnchecked(futureUnaryCall(call, req)); - } catch (RuntimeException e) { - throw cancelThrow(call, e); - } catch (Error e) { - throw cancelThrow(call, e); - } - } - - /** - * Executes a unary call and blocks on the response. The {@code call} should not be already - * started. After calling this method, {@code call} should no longer be used. - * - * @return the single response message. - * @throws StatusRuntimeException on error - */ - public static RespT blockingUnaryCall( - Channel channel, MethodDescriptor method, CallOptions callOptions, ReqT req) { - ThreadlessExecutor executor = new ThreadlessExecutor(); - boolean interrupt = false; - ClientCall call = - channel.newCall( - method, - callOptions - .withOption(ClientCalls.STUB_TYPE_OPTION, StubType.BLOCKING) - .withExecutor(executor)); - Histogram.Timer waitTimer = null; - try { - ListenableFuture responseFuture = futureUnaryCall(call, req); - waitTimer = blockingUnaryRequestWaitDuration.startTimer(); - while (!responseFuture.isDone()) { - try { - executor.waitAndDrain(); - } catch (InterruptedException e) { - interrupt = true; - call.cancel("Thread interrupted", e); - // Now wait for onClose() to be called, so interceptors can clean up - } - } - return getUnchecked(responseFuture); - } catch (RuntimeException e) { - // Something very bad happened. All bets are off; it may be dangerous to wait for onClose(). - throw cancelThrow(call, e); - } catch (Error e) { - // Something very bad happened. All bets are off; it may be dangerous to wait for onClose(). - throw cancelThrow(call, e); - } finally { - if (waitTimer != null) { - waitTimer.observeDuration(); - } - if (interrupt) { - Thread.currentThread().interrupt(); - } - } - } - - /** - * Executes a server-streaming call returning a blocking {@link Iterator} over the response - * stream. The {@code call} should not be already started. After calling this method, {@code call} - * should no longer be used. - * - *

The returned iterator may throw {@link StatusRuntimeException} on error. - * - * @return an iterator over the response stream. - */ - // TODO(louiscryan): Not clear if we want to use this idiom for 'simple' stubs. - public static Iterator blockingServerStreamingCall( - ClientCall call, ReqT req) { - BlockingResponseStream result = new BlockingResponseStream<>(call); - asyncUnaryRequestCall(call, req, result.listener()); - return result; - } - - /** - * Executes a server-streaming call returning a blocking {@link Iterator} over the response - * stream. The {@code call} should not be already started. After calling this method, {@code call} - * should no longer be used. - * - *

The returned iterator may throw {@link StatusRuntimeException} on error. - * - * @return an iterator over the response stream. - */ - // TODO(louiscryan): Not clear if we want to use this idiom for 'simple' stubs. - public static Iterator blockingServerStreamingCall( - Channel channel, MethodDescriptor method, CallOptions callOptions, ReqT req) { - ThreadlessExecutor executor = new ThreadlessExecutor(); - ClientCall call = - channel.newCall( - method, - callOptions - .withOption(ClientCalls.STUB_TYPE_OPTION, StubType.BLOCKING) - .withExecutor(executor)); - BlockingResponseStream result = new BlockingResponseStream<>(call, executor); - asyncUnaryRequestCall(call, req, result.listener()); - return result; - } - - /** - * Executes a unary call and returns a {@link ListenableFuture} to the response. The {@code call} - * should not be already started. After calling this method, {@code call} should no longer be - * used. - * - * @return a future for the single response message. - */ - public static ListenableFuture futureUnaryCall( - ClientCall call, ReqT req) { - GrpcFuture responseFuture = new GrpcFuture<>(call); - asyncUnaryRequestCall(call, req, new UnaryStreamToFuture<>(responseFuture)); - return responseFuture; - } - - /** - * Returns the result of calling {@link Future#get()} interruptibly on a task known not to throw a - * checked exception. - * - *

If interrupted, the interrupt is restored before throwing an exception.. - * - * @throws java.util.concurrent.CancellationException if {@code get} throws a {@code - * CancellationException}. - * @throws io.grpc.StatusRuntimeException if {@code get} throws an {@link ExecutionException} or - * an {@link InterruptedException}. - */ - private static V getUnchecked(Future future) { - try { - return future.get(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw Status.CANCELLED - .withDescription("Thread interrupted") - .withCause(e) - .asRuntimeException(); - } catch (ExecutionException e) { - throw toStatusRuntimeException(e.getCause()); - } - } - - /** - * Wraps the given {@link Throwable} in a {@link StatusRuntimeException}. If it contains an - * embedded {@link StatusException} or {@link StatusRuntimeException}, the returned exception will - * contain the embedded trailers and status, with the given exception as the cause. Otherwise, an - * exception will be generated from an {@link Status#UNKNOWN} status. - */ - private static StatusRuntimeException toStatusRuntimeException(Throwable t) { - Throwable cause = checkNotNull(t, "t"); - while (cause != null) { - // If we have an embedded status, use it and replace the cause - if (cause instanceof StatusException) { - StatusException se = (StatusException) cause; - return new StatusRuntimeException(se.getStatus(), se.getTrailers()); - } else if (cause instanceof StatusRuntimeException) { - StatusRuntimeException se = (StatusRuntimeException) cause; - return new StatusRuntimeException(se.getStatus(), se.getTrailers()); - } - cause = cause.getCause(); - } - return Status.UNKNOWN.withDescription("unexpected exception").withCause(t).asRuntimeException(); - } - - /** - * Cancels a call, and throws the exception. - * - * @param t must be a RuntimeException or Error - */ - private static RuntimeException cancelThrow(ClientCall call, Throwable t) { - try { - call.cancel(null, t); - } catch (Throwable e) { - assert e instanceof RuntimeException || e instanceof Error; - logger.log(Level.SEVERE, "RuntimeException encountered while closing call", e); - } - if (t instanceof RuntimeException) { - throw (RuntimeException) t; - } else if (t instanceof Error) { - throw (Error) t; - } - // should be impossible - throw new AssertionError(t); - } - - private static void asyncUnaryRequestCall( - ClientCall call, - ReqT req, - StreamObserver responseObserver, - boolean streamingResponse) { - asyncUnaryRequestCall( - call, - req, - new StreamObserverToCallListenerAdapter<>( - responseObserver, new CallToStreamObserverAdapter<>(call, streamingResponse))); - } - - private static void asyncUnaryRequestCall( - ClientCall call, ReqT req, StartableListener responseListener) { - Histogram.Timer startCallTimer = - asyncUnaryRequestCallDuration.labels("start_call").startTimer(); - startCall(call, responseListener); - startCallTimer.observeDuration(); - try { - Histogram.Timer sendMessageTimer = - asyncUnaryRequestCallDuration.labels("send_message").startTimer(); - call.sendMessage(req); - sendMessageTimer.observeDuration(); - - Histogram.Timer halfCloseTimer = - asyncUnaryRequestCallDuration.labels("half_close").startTimer(); - call.halfClose(); - halfCloseTimer.observeDuration(); - } catch (RuntimeException e) { - throw cancelThrow(call, e); - } catch (Error e) { - throw cancelThrow(call, e); - } - } - - private static StreamObserver asyncStreamingRequestCall( - ClientCall call, - StreamObserver responseObserver, - boolean streamingResponse) { - CallToStreamObserverAdapter adapter = - new CallToStreamObserverAdapter<>(call, streamingResponse); - startCall(call, new StreamObserverToCallListenerAdapter<>(responseObserver, adapter)); - return adapter; - } - - private static void startCall( - ClientCall call, StartableListener responseListener) { - call.start(responseListener, new Metadata()); - responseListener.onStart(); - } - - private abstract static class StartableListener extends ClientCall.Listener { - abstract void onStart(); - } - - private static final class CallToStreamObserverAdapter extends ClientCallStreamObserver { - private boolean frozen; - private final ClientCall call; - private final boolean streamingResponse; - private Runnable onReadyHandler; - private int initialRequest = 1; - private boolean autoRequestEnabled = true; - private boolean aborted = false; - private boolean completed = false; - - // Non private to avoid synthetic class - CallToStreamObserverAdapter(ClientCall call, boolean streamingResponse) { - this.call = call; - this.streamingResponse = streamingResponse; - } - - private void freeze() { - this.frozen = true; - } - - @Override - public void onNext(T value) { - checkState(!aborted, "Stream was terminated by error, no further calls are allowed"); - checkState(!completed, "Stream is already completed, no further calls are allowed"); - call.sendMessage(value); - } - - @Override - public void onError(Throwable t) { - call.cancel("Cancelled by client with StreamObserver.onError()", t); - aborted = true; - } - - @Override - public void onCompleted() { - call.halfClose(); - completed = true; - } - - @Override - public boolean isReady() { - return call.isReady(); - } - - @Override - public void setOnReadyHandler(Runnable onReadyHandler) { - if (frozen) { - throw new IllegalStateException( - "Cannot alter onReadyHandler after call started. Use ClientResponseObserver"); - } - this.onReadyHandler = onReadyHandler; - } - - @Deprecated - @Override - public void disableAutoInboundFlowControl() { - disableAutoRequestWithInitial(1); - } - - @Override - public void disableAutoRequestWithInitial(int request) { - if (frozen) { - throw new IllegalStateException( - "Cannot disable auto flow control after call started. Use ClientResponseObserver"); - } - Preconditions.checkArgument(request >= 0, "Initial requests must be non-negative"); - initialRequest = request; - autoRequestEnabled = false; - } - - @Override - public void request(int count) { - if (!streamingResponse && count == 1) { - // Initially ask for two responses from flow-control so that if a misbehaving server - // sends more than one responses, we can catch it and fail it in the listener. - call.request(2); - } else { - call.request(count); - } - } - - @Override - public void setMessageCompression(boolean enable) { - call.setMessageCompression(enable); - } - - @Override - public void cancel(@Nullable String message, @Nullable Throwable cause) { - call.cancel(message, cause); - } - } - - private static final class StreamObserverToCallListenerAdapter - extends StartableListener { - private final StreamObserver observer; - private final CallToStreamObserverAdapter adapter; - private boolean firstResponseReceived; - - // Non private to avoid synthetic class - StreamObserverToCallListenerAdapter( - StreamObserver observer, CallToStreamObserverAdapter adapter) { - this.observer = observer; - this.adapter = adapter; - if (observer instanceof ClientResponseObserver) { - @SuppressWarnings("unchecked") - ClientResponseObserver clientResponseObserver = - (ClientResponseObserver) observer; - clientResponseObserver.beforeStart(adapter); - } - adapter.freeze(); - } - - @Override - public void onHeaders(Metadata headers) {} - - @Override - public void onMessage(RespT message) { - if (firstResponseReceived && !adapter.streamingResponse) { - throw Status.INTERNAL - .withDescription("More than one responses received for unary or client-streaming call") - .asRuntimeException(); - } - firstResponseReceived = true; - observer.onNext(message); - - if (adapter.streamingResponse && adapter.autoRequestEnabled) { - // Request delivery of the next inbound message. - adapter.request(1); - } - } - - @Override - public void onClose(Status status, Metadata trailers) { - if (status.isOk()) { - observer.onCompleted(); - } else { - observer.onError(status.asRuntimeException(trailers)); - } - } - - @Override - public void onReady() { - if (adapter.onReadyHandler != null) { - adapter.onReadyHandler.run(); - } - } - - @Override - void onStart() { - if (adapter.initialRequest > 0) { - adapter.request(adapter.initialRequest); - } - } - } - - /** Completes a {@link GrpcFuture} using {@link StreamObserver} events. */ - private static final class UnaryStreamToFuture extends StartableListener { - private final GrpcFuture responseFuture; - private RespT value; - - // Non private to avoid synthetic class - UnaryStreamToFuture(GrpcFuture responseFuture) { - this.responseFuture = responseFuture; - } - - @Override - public void onHeaders(Metadata headers) {} - - @Override - public void onMessage(RespT value) { - if (this.value != null) { - throw Status.INTERNAL - .withDescription("More than one value received for unary call") - .asRuntimeException(); - } - this.value = value; - } - - @Override - public void onClose(Status status, Metadata trailers) { - if (status.isOk()) { - if (value == null) { - // No value received so mark the future as an error - responseFuture.setException( - Status.INTERNAL - .withDescription("No value received for unary call") - .asRuntimeException(trailers)); - } - responseFuture.set(value); - } else { - responseFuture.setException(status.asRuntimeException(trailers)); - } - } - - @Override - void onStart() { - responseFuture.call.request(2); - } - } - - private static final class GrpcFuture extends AbstractFuture { - private final ClientCall call; - - // Non private to avoid synthetic class - GrpcFuture(ClientCall call) { - this.call = call; - } - - @Override - protected void interruptTask() { - call.cancel("GrpcFuture was cancelled", null); - } - - @Override - protected boolean set(@Nullable RespT resp) { - return super.set(resp); - } - - @Override - protected boolean setException(Throwable throwable) { - return super.setException(throwable); - } - - @SuppressWarnings("MissingOverride") // Add @Override once Java 6 support is dropped - protected String pendingToString() { - return MoreObjects.toStringHelper(this).add("clientCall", call).toString(); - } - } - - /** - * Convert events on a {@link io.grpc.ClientCall.Listener} into a blocking {@link Iterator}. - * - *

The class is not thread-safe, but it does permit {@link ClientCall.Listener} calls in a - * separate thread from {@link Iterator} calls. - */ - // TODO(ejona86): determine how to allow ClientCall.cancel() in case of application error. - private static final class BlockingResponseStream implements Iterator { - // Due to flow control, only needs to hold up to 3 items: 2 for value, 1 for close. - // (2 for value, not 1, because of early request() in next()) - private final BlockingQueue buffer = new ArrayBlockingQueue<>(3); - private final StartableListener listener = new QueuingListener(); - private final ClientCall call; - /** May be null. */ - private final ThreadlessExecutor threadless; - // Only accessed when iterating. - private Object last; - - // Non private to avoid synthetic class - BlockingResponseStream(ClientCall call) { - this(call, null); - } - - // Non private to avoid synthetic class - BlockingResponseStream(ClientCall call, ThreadlessExecutor threadless) { - this.call = call; - this.threadless = threadless; - } - - StartableListener listener() { - return listener; - } - - private Object waitForNext() { - boolean interrupt = false; - try { - if (threadless == null) { - while (true) { - try { - return buffer.take(); - } catch (InterruptedException ie) { - interrupt = true; - call.cancel("Thread interrupted", ie); - // Now wait for onClose() to be called, to guarantee BlockingQueue doesn't fill - } - } - } else { - Object next; - while ((next = buffer.poll()) == null) { - try { - threadless.waitAndDrain(); - } catch (InterruptedException ie) { - interrupt = true; - call.cancel("Thread interrupted", ie); - // Now wait for onClose() to be called, so interceptors can clean up - } - } - return next; - } - } finally { - if (interrupt) { - Thread.currentThread().interrupt(); - } - } - } - - @Override - public boolean hasNext() { - while (last == null) { - // Will block here indefinitely waiting for content. RPC timeouts defend against permanent - // hangs here as the call will become closed. - last = waitForNext(); - } - if (last instanceof StatusRuntimeException) { - // Rethrow the exception with a new stacktrace. - StatusRuntimeException e = (StatusRuntimeException) last; - throw e.getStatus().asRuntimeException(e.getTrailers()); - } - return last != this; - } - - @Override - public T next() { - // Eagerly call request(1) so it can be processing the next message while we wait for the - // current one, which reduces latency for the next message. With MigratingThreadDeframer and - // if the data has already been recieved, every other message can be delivered instantly. This - // can be run after hasNext(), but just would be slower. - if (!(last instanceof StatusRuntimeException) && last != this) { - call.request(1); - } - if (!hasNext()) { - throw new NoSuchElementException(); - } - @SuppressWarnings("unchecked") - T tmp = (T) last; - last = null; - return tmp; - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - - private final class QueuingListener extends StartableListener { - // Non private to avoid synthetic class - QueuingListener() {} - - private boolean done = false; - - @Override - public void onHeaders(Metadata headers) {} - - @Override - public void onMessage(T value) { - Preconditions.checkState(!done, "ClientCall already closed"); - buffer.add(value); - } - - @Override - public void onClose(Status status, Metadata trailers) { - Preconditions.checkState(!done, "ClientCall already closed"); - if (status.isOk()) { - buffer.add(BlockingResponseStream.this); - } else { - buffer.add(status.asRuntimeException(trailers)); - } - done = true; - } - - @Override - void onStart() { - call.request(1); - } - } - } - - @SuppressWarnings("serial") - private static final class ThreadlessExecutor extends ConcurrentLinkedQueue - implements Executor { - private static final Logger log = Logger.getLogger(ThreadlessExecutor.class.getName()); - - private volatile Thread waiter; - private static final Histogram lockDuration = - HistogramUtils.buildDuration() - .name("grpc_client_executor_lock_duration_seconds") - .help("Histogram of time spent in ThreadlessExecutor lock") - .labelNames("phase") - .register(); - - // Non private to avoid synthetic class - ThreadlessExecutor() {} - - /** - * Waits until there is a Runnable, then executes it and all queued Runnables after it. Must - * only be called by one thread at a time. - */ - public void waitAndDrain() throws InterruptedException { - throwIfInterrupted(); - Runnable runnable = poll(); - if (runnable == null) { - waiter = Thread.currentThread(); - try { - Histogram.Timer parkTimer = lockDuration.labels("park").startTimer(); - while ((runnable = poll()) == null) { - LockSupport.park(this); - throwIfInterrupted(); - } - parkTimer.observeDuration(); - } finally { - waiter = null; - } - } - do { - try { - runnable.run(); - } catch (Throwable t) { - log.log(Level.WARNING, "Runnable threw exception", t); - } - } while ((runnable = poll()) != null); - } - - private static void throwIfInterrupted() throws InterruptedException { - if (Thread.interrupted()) { - throw new InterruptedException(); - } - } - - @Override - public void execute(Runnable runnable) { - add(runnable); - Histogram.Timer unparkTimer = lockDuration.labels("unpark").startTimer(); - LockSupport.unpark(waiter); // no-op if null - unparkTimer.observeDuration(); - } - } - - enum StubType { - BLOCKING, - FUTURE, - ASYNC - } - - /** Internal {@link CallOptions.Key} to indicate stub types. */ - static final CallOptions.Key STUB_TYPE_OPTION = - CallOptions.Key.create("internal-stub-type"); -} diff --git a/src/main/java/io/netty/buffer/PoolArena.java b/src/main/java/io/netty/buffer/PoolArena.java deleted file mode 100644 index fc6cbf258ae..00000000000 --- a/src/main/java/io/netty/buffer/PoolArena.java +++ /dev/null @@ -1,752 +0,0 @@ -/* - * Copyright 2012 The Netty Project - * - * The Netty Project licenses this file to you under the Apache License, - * version 2.0 (the "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, 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. - */ - -package io.netty.buffer; - -import static io.netty.buffer.PoolChunk.isSubpage; -import static java.lang.Math.max; - -import io.netty.util.internal.LongCounter; -import io.netty.util.internal.PlatformDependent; -import io.netty.util.internal.StringUtil; -import io.prometheus.client.Counter; -import io.prometheus.client.Histogram; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; - -abstract class PoolArena extends SizeClasses implements PoolArenaMetric { - - public static final Counter poolArenaAllocations = - Counter.build() - .name("netty_buffer_pool_arena_allocations") - .help("Number of times a pool arena was allocated") - .labelNames("type") - .register(); - - public static final Histogram poolArenaAllocationsDuration = - Histogram.build() - .name("netty_buffer_pool_arena_allocations_duration_seconds") - .help("Duration of a pool arena allocation") - .labelNames("type") - .register(); - - static final boolean HAS_UNSAFE = PlatformDependent.hasUnsafe(); - - enum SizeClass { - Small, - Normal - } - - final PooledByteBufAllocator parent; - - final int numSmallSubpagePools; - final int directMemoryCacheAlignment; - private final PoolSubpage[] smallSubpagePools; - - private final PoolChunkList q050; - private final PoolChunkList q025; - private final PoolChunkList q000; - private final PoolChunkList qInit; - private final PoolChunkList q075; - private final PoolChunkList q100; - - private final List chunkListMetrics; - - // Metrics for allocations and deallocations - private long allocationsNormal; - // We need to use the LongCounter here as this is not guarded via synchronized block. - private final LongCounter allocationsSmall = PlatformDependent.newLongCounter(); - private final LongCounter allocationsHuge = PlatformDependent.newLongCounter(); - private final LongCounter activeBytesHuge = PlatformDependent.newLongCounter(); - - private long deallocationsSmall; - private long deallocationsNormal; - - // We need to use the LongCounter here as this is not guarded via synchronized block. - private final LongCounter deallocationsHuge = PlatformDependent.newLongCounter(); - - // Number of thread caches backed by this arena. - final AtomicInteger numThreadCaches = new AtomicInteger(); - - // TODO: Test if adding padding helps under contention - // private long pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7; - - protected PoolArena( - PooledByteBufAllocator parent, - int pageSize, - int pageShifts, - int chunkSize, - int cacheAlignment) { - super(pageSize, pageShifts, chunkSize, cacheAlignment); - this.parent = parent; - directMemoryCacheAlignment = cacheAlignment; - - numSmallSubpagePools = nSubpages; - smallSubpagePools = newSubpagePoolArray(numSmallSubpagePools); - for (int i = 0; i < smallSubpagePools.length; i++) { - smallSubpagePools[i] = newSubpagePoolHead(); - } - - q100 = new PoolChunkList(this, null, 100, Integer.MAX_VALUE, chunkSize); - q075 = new PoolChunkList(this, q100, 75, 100, chunkSize); - q050 = new PoolChunkList(this, q075, 50, 100, chunkSize); - q025 = new PoolChunkList(this, q050, 25, 75, chunkSize); - q000 = new PoolChunkList(this, q025, 1, 50, chunkSize); - qInit = new PoolChunkList(this, q000, Integer.MIN_VALUE, 25, chunkSize); - - q100.prevList(q075); - q075.prevList(q050); - q050.prevList(q025); - q025.prevList(q000); - q000.prevList(null); - qInit.prevList(qInit); - - List metrics = new ArrayList(6); - metrics.add(qInit); - metrics.add(q000); - metrics.add(q025); - metrics.add(q050); - metrics.add(q075); - metrics.add(q100); - chunkListMetrics = Collections.unmodifiableList(metrics); - } - - private PoolSubpage newSubpagePoolHead() { - PoolSubpage head = new PoolSubpage(); - head.prev = head; - head.next = head; - return head; - } - - @SuppressWarnings("unchecked") - private PoolSubpage[] newSubpagePoolArray(int size) { - return new PoolSubpage[size]; - } - - abstract boolean isDirect(); - - PooledByteBuf allocate(PoolThreadCache cache, int reqCapacity, int maxCapacity) { - PooledByteBuf buf = newByteBuf(maxCapacity); - allocate(cache, buf, reqCapacity); - return buf; - } - - private void allocate(PoolThreadCache cache, PooledByteBuf buf, final int reqCapacity) { - final int sizeIdx = size2SizeIdx(reqCapacity); - - if (sizeIdx <= smallMaxSizeIdx) { - Histogram.Timer smallAllocationTimer = - poolArenaAllocationsDuration.labels("small").startTimer(); - tcacheAllocateSmall(cache, buf, reqCapacity, sizeIdx); - smallAllocationTimer.observeDuration(); - poolArenaAllocations.labels("small").inc(); - } else if (sizeIdx < nSizes) { - Histogram.Timer normalAllocationTimer = - poolArenaAllocationsDuration.labels("normal").startTimer(); - tcacheAllocateNormal(cache, buf, reqCapacity, sizeIdx); - normalAllocationTimer.observeDuration(); - poolArenaAllocations.labels("normal").inc(); - } else { - Histogram.Timer hugeAllocationTimer = - poolArenaAllocationsDuration.labels("huge").startTimer(); - int normCapacity = directMemoryCacheAlignment > 0 ? normalizeSize(reqCapacity) : reqCapacity; - // Huge allocations are never served via the cache so just call allocateHuge - allocateHuge(buf, normCapacity); - hugeAllocationTimer.observeDuration(); - poolArenaAllocations.labels("huge").inc(); - } - } - - private void tcacheAllocateSmall( - PoolThreadCache cache, PooledByteBuf buf, final int reqCapacity, final int sizeIdx) { - - if (cache.allocateSmall(this, buf, reqCapacity, sizeIdx)) { - // was able to allocate out of the cache so move on - return; - } - - /* - * Synchronize on the head. This is needed as {@link PoolChunk#allocateSubpage(int)} and - * {@link PoolChunk#free(long)} may modify the doubly linked list as well. - */ - final PoolSubpage head = smallSubpagePools[sizeIdx]; - final boolean needsNormalAllocation; - synchronized (head) { - final PoolSubpage s = head.next; - needsNormalAllocation = s == head; - if (!needsNormalAllocation) { - assert s.doNotDestroy && s.elemSize == sizeIdx2size(sizeIdx) - : "doNotDestroy=" - + s.doNotDestroy - + ", elemSize=" - + s.elemSize - + ", sizeIdx=" - + sizeIdx; - long handle = s.allocate(); - assert handle >= 0; - s.chunk.initBufWithSubpage(buf, null, handle, reqCapacity, cache); - } - } - - if (needsNormalAllocation) { - synchronized (this) { - allocateNormal(buf, reqCapacity, sizeIdx, cache); - } - } - - incSmallAllocation(); - } - - private void tcacheAllocateNormal( - PoolThreadCache cache, PooledByteBuf buf, final int reqCapacity, final int sizeIdx) { - if (cache.allocateNormal(this, buf, reqCapacity, sizeIdx)) { - // was able to allocate out of the cache so move on - return; - } - synchronized (this) { - allocateNormal(buf, reqCapacity, sizeIdx, cache); - ++allocationsNormal; - } - } - - // Method must be called inside synchronized(this) { ... } block - private void allocateNormal( - PooledByteBuf buf, int reqCapacity, int sizeIdx, PoolThreadCache threadCache) { - if (q050.allocate(buf, reqCapacity, sizeIdx, threadCache) - || q025.allocate(buf, reqCapacity, sizeIdx, threadCache) - || q000.allocate(buf, reqCapacity, sizeIdx, threadCache) - || qInit.allocate(buf, reqCapacity, sizeIdx, threadCache) - || q075.allocate(buf, reqCapacity, sizeIdx, threadCache)) { - return; - } - - // Add a new chunk. - PoolChunk c = newChunk(pageSize, nPSizes, pageShifts, chunkSize); - boolean success = c.allocate(buf, reqCapacity, sizeIdx, threadCache); - assert success; - qInit.add(c); - } - - private void incSmallAllocation() { - allocationsSmall.increment(); - } - - private void allocateHuge(PooledByteBuf buf, int reqCapacity) { - PoolChunk chunk = newUnpooledChunk(reqCapacity); - activeBytesHuge.add(chunk.chunkSize()); - buf.initUnpooled(chunk, reqCapacity); - allocationsHuge.increment(); - } - - void free( - PoolChunk chunk, - ByteBuffer nioBuffer, - long handle, - int normCapacity, - PoolThreadCache cache) { - if (chunk.unpooled) { - int size = chunk.chunkSize(); - destroyChunk(chunk); - activeBytesHuge.add(-size); - deallocationsHuge.increment(); - } else { - SizeClass sizeClass = sizeClass(handle); - if (cache != null && cache.add(this, chunk, nioBuffer, handle, normCapacity, sizeClass)) { - // cached so not free it. - return; - } - - freeChunk(chunk, handle, normCapacity, sizeClass, nioBuffer, false); - } - } - - private static SizeClass sizeClass(long handle) { - return isSubpage(handle) ? SizeClass.Small : SizeClass.Normal; - } - - void freeChunk( - PoolChunk chunk, - long handle, - int normCapacity, - SizeClass sizeClass, - ByteBuffer nioBuffer, - boolean finalizer) { - final boolean destroyChunk; - synchronized (this) { - // We only call this if freeChunk is not called because of the PoolThreadCache finalizer as - // otherwise this - // may fail due lazy class-loading in for example tomcat. - if (!finalizer) { - switch (sizeClass) { - case Normal: - ++deallocationsNormal; - break; - case Small: - ++deallocationsSmall; - break; - default: - throw new Error(); - } - } - destroyChunk = !chunk.parent.free(chunk, handle, normCapacity, nioBuffer); - } - if (destroyChunk) { - // destroyChunk not need to be called while holding the synchronized lock. - destroyChunk(chunk); - } - } - - PoolSubpage findSubpagePoolHead(int sizeIdx) { - return smallSubpagePools[sizeIdx]; - } - - void reallocate(PooledByteBuf buf, int newCapacity, boolean freeOldMemory) { - assert newCapacity >= 0 && newCapacity <= buf.maxCapacity(); - - int oldCapacity = buf.length; - if (oldCapacity == newCapacity) { - return; - } - - PoolChunk oldChunk = buf.chunk; - ByteBuffer oldNioBuffer = buf.tmpNioBuf; - long oldHandle = buf.handle; - T oldMemory = buf.memory; - int oldOffset = buf.offset; - int oldMaxLength = buf.maxLength; - - // This does not touch buf's reader/writer indices - allocate(parent.threadCache(), buf, newCapacity); - int bytesToCopy; - if (newCapacity > oldCapacity) { - bytesToCopy = oldCapacity; - } else { - buf.trimIndicesToCapacity(newCapacity); - bytesToCopy = newCapacity; - } - memoryCopy(oldMemory, oldOffset, buf, bytesToCopy); - if (freeOldMemory) { - free(oldChunk, oldNioBuffer, oldHandle, oldMaxLength, buf.cache); - } - } - - @Override - public int numThreadCaches() { - return numThreadCaches.get(); - } - - @Override - public int numTinySubpages() { - return 0; - } - - @Override - public int numSmallSubpages() { - return smallSubpagePools.length; - } - - @Override - public int numChunkLists() { - return chunkListMetrics.size(); - } - - @Override - public List tinySubpages() { - return Collections.emptyList(); - } - - @Override - public List smallSubpages() { - return subPageMetricList(smallSubpagePools); - } - - @Override - public List chunkLists() { - return chunkListMetrics; - } - - private static List subPageMetricList(PoolSubpage[] pages) { - List metrics = new ArrayList(); - for (PoolSubpage head : pages) { - if (head.next == head) { - continue; - } - PoolSubpage s = head.next; - for (; ; ) { - metrics.add(s); - s = s.next; - if (s == head) { - break; - } - } - } - return metrics; - } - - @Override - public long numAllocations() { - final long allocsNormal; - synchronized (this) { - allocsNormal = allocationsNormal; - } - return allocationsSmall.value() + allocsNormal + allocationsHuge.value(); - } - - @Override - public long numTinyAllocations() { - return 0; - } - - @Override - public long numSmallAllocations() { - return allocationsSmall.value(); - } - - @Override - public synchronized long numNormalAllocations() { - return allocationsNormal; - } - - @Override - public long numDeallocations() { - final long deallocs; - synchronized (this) { - deallocs = deallocationsSmall + deallocationsNormal; - } - return deallocs + deallocationsHuge.value(); - } - - @Override - public long numTinyDeallocations() { - return 0; - } - - @Override - public synchronized long numSmallDeallocations() { - return deallocationsSmall; - } - - @Override - public synchronized long numNormalDeallocations() { - return deallocationsNormal; - } - - @Override - public long numHugeAllocations() { - return allocationsHuge.value(); - } - - @Override - public long numHugeDeallocations() { - return deallocationsHuge.value(); - } - - @Override - public long numActiveAllocations() { - long val = allocationsSmall.value() + allocationsHuge.value() - deallocationsHuge.value(); - synchronized (this) { - val += allocationsNormal - (deallocationsSmall + deallocationsNormal); - } - return max(val, 0); - } - - @Override - public long numActiveTinyAllocations() { - return 0; - } - - @Override - public long numActiveSmallAllocations() { - return max(numSmallAllocations() - numSmallDeallocations(), 0); - } - - @Override - public long numActiveNormalAllocations() { - final long val; - synchronized (this) { - val = allocationsNormal - deallocationsNormal; - } - return max(val, 0); - } - - @Override - public long numActiveHugeAllocations() { - return max(numHugeAllocations() - numHugeDeallocations(), 0); - } - - @Override - public long numActiveBytes() { - long val = activeBytesHuge.value(); - synchronized (this) { - for (int i = 0; i < chunkListMetrics.size(); i++) { - for (PoolChunkMetric m : chunkListMetrics.get(i)) { - val += m.chunkSize(); - } - } - } - return max(0, val); - } - - /** - * Return the number of bytes that are currently pinned to buffer instances, by the arena. The - * pinned memory is not accessible for use by any other allocation, until the buffers using have - * all been released. - */ - public long numPinnedBytes() { - long val = - activeBytesHuge - .value(); // Huge chunks are exact-sized for the buffers they were allocated to. - synchronized (this) { - for (int i = 0; i < chunkListMetrics.size(); i++) { - for (PoolChunkMetric m : chunkListMetrics.get(i)) { - val += ((PoolChunk) m).pinnedBytes(); - } - } - } - return max(0, val); - } - - protected abstract PoolChunk newChunk( - int pageSize, int maxPageIdx, int pageShifts, int chunkSize); - - protected abstract PoolChunk newUnpooledChunk(int capacity); - - protected abstract PooledByteBuf newByteBuf(int maxCapacity); - - protected abstract void memoryCopy(T src, int srcOffset, PooledByteBuf dst, int length); - - protected abstract void destroyChunk(PoolChunk chunk); - - @Override - public synchronized String toString() { - StringBuilder buf = - new StringBuilder() - .append("Chunk(s) at 0~25%:") - .append(StringUtil.NEWLINE) - .append(qInit) - .append(StringUtil.NEWLINE) - .append("Chunk(s) at 0~50%:") - .append(StringUtil.NEWLINE) - .append(q000) - .append(StringUtil.NEWLINE) - .append("Chunk(s) at 25~75%:") - .append(StringUtil.NEWLINE) - .append(q025) - .append(StringUtil.NEWLINE) - .append("Chunk(s) at 50~100%:") - .append(StringUtil.NEWLINE) - .append(q050) - .append(StringUtil.NEWLINE) - .append("Chunk(s) at 75~100%:") - .append(StringUtil.NEWLINE) - .append(q075) - .append(StringUtil.NEWLINE) - .append("Chunk(s) at 100%:") - .append(StringUtil.NEWLINE) - .append(q100) - .append(StringUtil.NEWLINE) - .append("small subpages:"); - appendPoolSubPages(buf, smallSubpagePools); - buf.append(StringUtil.NEWLINE); - - return buf.toString(); - } - - private static void appendPoolSubPages(StringBuilder buf, PoolSubpage[] subpages) { - for (int i = 0; i < subpages.length; i++) { - PoolSubpage head = subpages[i]; - if (head.next == head) { - continue; - } - - buf.append(StringUtil.NEWLINE).append(i).append(": "); - PoolSubpage s = head.next; - for (; ; ) { - buf.append(s); - s = s.next; - if (s == head) { - break; - } - } - } - } - - @Override - protected final void finalize() throws Throwable { - try { - super.finalize(); - } finally { - destroyPoolSubPages(smallSubpagePools); - destroyPoolChunkLists(qInit, q000, q025, q050, q075, q100); - } - } - - private static void destroyPoolSubPages(PoolSubpage[] pages) { - for (PoolSubpage page : pages) { - page.destroy(); - } - } - - private void destroyPoolChunkLists(PoolChunkList... chunkLists) { - for (PoolChunkList chunkList : chunkLists) { - chunkList.destroy(this); - } - } - - static final class HeapArena extends PoolArena { - - HeapArena(PooledByteBufAllocator parent, int pageSize, int pageShifts, int chunkSize) { - super(parent, pageSize, pageShifts, chunkSize, 0); - } - - private static byte[] newByteArray(int size) { - return PlatformDependent.allocateUninitializedArray(size); - } - - @Override - boolean isDirect() { - return false; - } - - @Override - protected PoolChunk newChunk( - int pageSize, int maxPageIdx, int pageShifts, int chunkSize) { - return new PoolChunk( - this, null, newByteArray(chunkSize), pageSize, pageShifts, chunkSize, maxPageIdx); - } - - @Override - protected PoolChunk newUnpooledChunk(int capacity) { - return new PoolChunk(this, null, newByteArray(capacity), capacity); - } - - @Override - protected void destroyChunk(PoolChunk chunk) { - // Rely on GC. - } - - @Override - protected PooledByteBuf newByteBuf(int maxCapacity) { - return HAS_UNSAFE - ? PooledUnsafeHeapByteBuf.newUnsafeInstance(maxCapacity) - : PooledHeapByteBuf.newInstance(maxCapacity); - } - - @Override - protected void memoryCopy(byte[] src, int srcOffset, PooledByteBuf dst, int length) { - if (length == 0) { - return; - } - - System.arraycopy(src, srcOffset, dst.memory, dst.offset, length); - } - } - - static final class DirectArena extends PoolArena { - - DirectArena( - PooledByteBufAllocator parent, - int pageSize, - int pageShifts, - int chunkSize, - int directMemoryCacheAlignment) { - super(parent, pageSize, pageShifts, chunkSize, directMemoryCacheAlignment); - } - - @Override - boolean isDirect() { - return true; - } - - @Override - protected PoolChunk newChunk( - int pageSize, int maxPageIdx, int pageShifts, int chunkSize) { - if (directMemoryCacheAlignment == 0) { - ByteBuffer memory = allocateDirect(chunkSize); - return new PoolChunk( - this, memory, memory, pageSize, pageShifts, chunkSize, maxPageIdx); - } - - final ByteBuffer base = allocateDirect(chunkSize + directMemoryCacheAlignment); - final ByteBuffer memory = - PlatformDependent.alignDirectBuffer(base, directMemoryCacheAlignment); - return new PoolChunk( - this, base, memory, pageSize, pageShifts, chunkSize, maxPageIdx); - } - - @Override - protected PoolChunk newUnpooledChunk(int capacity) { - if (directMemoryCacheAlignment == 0) { - ByteBuffer memory = allocateDirect(capacity); - return new PoolChunk(this, memory, memory, capacity); - } - - final ByteBuffer base = allocateDirect(capacity + directMemoryCacheAlignment); - final ByteBuffer memory = - PlatformDependent.alignDirectBuffer(base, directMemoryCacheAlignment); - return new PoolChunk(this, base, memory, capacity); - } - - private static ByteBuffer allocateDirect(int capacity) { - return PlatformDependent.useDirectBufferNoCleaner() - ? PlatformDependent.allocateDirectNoCleaner(capacity) - : ByteBuffer.allocateDirect(capacity); - } - - @Override - protected void destroyChunk(PoolChunk chunk) { - if (PlatformDependent.useDirectBufferNoCleaner()) { - PlatformDependent.freeDirectNoCleaner((ByteBuffer) chunk.base); - } else { - PlatformDependent.freeDirectBuffer((ByteBuffer) chunk.base); - } - } - - @Override - protected PooledByteBuf newByteBuf(int maxCapacity) { - if (HAS_UNSAFE) { - return PooledUnsafeDirectByteBuf.newInstance(maxCapacity); - } else { - return PooledDirectByteBuf.newInstance(maxCapacity); - } - } - - @Override - protected void memoryCopy( - ByteBuffer src, int srcOffset, PooledByteBuf dstBuf, int length) { - if (length == 0) { - return; - } - - if (HAS_UNSAFE) { - PlatformDependent.copyMemory( - PlatformDependent.directBufferAddress(src) + srcOffset, - PlatformDependent.directBufferAddress(dstBuf.memory) + dstBuf.offset, - length); - } else { - // We must duplicate the NIO buffers because they may be accessed by other Netty buffers. - src = src.duplicate(); - ByteBuffer dst = dstBuf.internalNioBuffer(); - src.position(srcOffset).limit(srcOffset + length); - dst.position(dstBuf.offset); - dst.put(src); - } - } - } -} diff --git a/src/main/java/io/netty/channel/socket/nio/NioSocketChannel.java b/src/main/java/io/netty/channel/socket/nio/NioSocketChannel.java deleted file mode 100644 index 1a95ff9c5ba..00000000000 --- a/src/main/java/io/netty/channel/socket/nio/NioSocketChannel.java +++ /dev/null @@ -1,602 +0,0 @@ -/* - * Copyright 2012 The Netty Project - * - * The Netty Project licenses this file to you under the Apache License, - * version 2.0 (the "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, 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. - */ -package io.netty.channel.socket.nio; - -import static io.netty.channel.internal.ChannelUtils.MAX_BYTES_PER_GATHERING_WRITE_ATTEMPTED_LOW_THRESHOLD; - -import io.netty.buffer.ByteBuf; -import io.netty.channel.Channel; -import io.netty.channel.ChannelException; -import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelFutureListener; -import io.netty.channel.ChannelOption; -import io.netty.channel.ChannelOutboundBuffer; -import io.netty.channel.ChannelPromise; -import io.netty.channel.EventLoop; -import io.netty.channel.FileRegion; -import io.netty.channel.RecvByteBufAllocator; -import io.netty.channel.nio.AbstractNioByteChannel; -import io.netty.channel.socket.DefaultSocketChannelConfig; -import io.netty.channel.socket.ServerSocketChannel; -import io.netty.channel.socket.SocketChannelConfig; -import io.netty.util.concurrent.GlobalEventExecutor; -import io.netty.util.internal.PlatformDependent; -import io.netty.util.internal.SocketUtils; -import io.netty.util.internal.SuppressJava6Requirement; -import io.netty.util.internal.UnstableApi; -import io.netty.util.internal.logging.InternalLogger; -import io.netty.util.internal.logging.InternalLoggerFactory; -import io.prometheus.client.Histogram; -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.net.SocketAddress; -import java.nio.ByteBuffer; -import java.nio.channels.SelectionKey; -import java.nio.channels.SocketChannel; -import java.nio.channels.spi.SelectorProvider; -import java.util.Map; -import java.util.concurrent.Executor; -import org.tikv.common.util.HistogramUtils; - -/** {@link io.netty.channel.socket.SocketChannel} which uses NIO selector based implementation. */ -public class NioSocketChannel extends AbstractNioByteChannel - implements io.netty.channel.socket.SocketChannel { - public static final Histogram socketWriteDuration = - HistogramUtils.buildDuration() - .name("netty_nio_socket_channel_write_duration_seconds") - .help("Time taken to write data to socket") - .register(); - public static final Histogram socketWriteBytes = - HistogramUtils.buildBytes() - .name("netty_nio_socket_channel_write_bytes") - .help("number of bytes for each write call") - .register(); - public static final Histogram socketWrittenBytes = - HistogramUtils.buildBytes() - .name("netty_nio_socket_channel_written_bytes") - .help("number of bytes actually written for each write call") - .register(); - public static final Histogram socketWriteLeftBytes = - HistogramUtils.buildBytes() - .name("netty_nio_socket_channel_write_left_bytes") - .help("number of bytes not written for each write call") - .register(); - public static final Histogram socketReadDuration = - HistogramUtils.buildDuration() - .name("netty_nio_socket_channel_read_duration_seconds") - .help("Time taken to read data to socket") - .register(); - public static final Histogram socketReadBytes = - HistogramUtils.buildBytes() - .name("netty_nio_socket_channel_read_bytes") - .help("number of bytes for each read call") - .register(); - - public static final Histogram socketReadLeftBytes = - HistogramUtils.buildBytes() - .name("netty_nio_socket_channel_read_left_bytes") - .help("number of bytes not read for each read call") - .register(); - - private static final InternalLogger logger = - InternalLoggerFactory.getInstance(NioSocketChannel.class); - private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider(); - - private static SocketChannel newSocket(SelectorProvider provider) { - try { - /** - * Use the {@link SelectorProvider} to open {@link SocketChannel} and so remove condition in - * {@link SelectorProvider#provider()} which is called by each SocketChannel.open() otherwise. - * - *

See #2308. - */ - return provider.openSocketChannel(); - } catch (IOException e) { - throw new ChannelException("Failed to open a socket.", e); - } - } - - private final SocketChannelConfig config; - - /** Create a new instance */ - public NioSocketChannel() { - this(DEFAULT_SELECTOR_PROVIDER); - } - - /** Create a new instance using the given {@link SelectorProvider}. */ - public NioSocketChannel(SelectorProvider provider) { - this(newSocket(provider)); - } - - /** Create a new instance using the given {@link SocketChannel}. */ - public NioSocketChannel(SocketChannel socket) { - this(null, socket); - } - - /** - * Create a new instance - * - * @param parent the {@link Channel} which created this instance or {@code null} if it was created - * by the user - * @param socket the {@link SocketChannel} which will be used - */ - public NioSocketChannel(Channel parent, SocketChannel socket) { - super(parent, socket); - config = new NioSocketChannelConfig(this, socket.socket()); - } - - @Override - public ServerSocketChannel parent() { - return (ServerSocketChannel) super.parent(); - } - - @Override - public SocketChannelConfig config() { - return config; - } - - @Override - protected SocketChannel javaChannel() { - return (SocketChannel) super.javaChannel(); - } - - @Override - public boolean isActive() { - SocketChannel ch = javaChannel(); - return ch.isOpen() && ch.isConnected(); - } - - @Override - public boolean isOutputShutdown() { - return javaChannel().socket().isOutputShutdown() || !isActive(); - } - - @Override - public boolean isInputShutdown() { - return javaChannel().socket().isInputShutdown() || !isActive(); - } - - @Override - public boolean isShutdown() { - Socket socket = javaChannel().socket(); - return socket.isInputShutdown() && socket.isOutputShutdown() || !isActive(); - } - - @Override - public InetSocketAddress localAddress() { - return (InetSocketAddress) super.localAddress(); - } - - @Override - public InetSocketAddress remoteAddress() { - return (InetSocketAddress) super.remoteAddress(); - } - - @SuppressJava6Requirement(reason = "Usage guarded by java version check") - @UnstableApi - @Override - protected final void doShutdownOutput() throws Exception { - if (PlatformDependent.javaVersion() >= 7) { - javaChannel().shutdownOutput(); - } else { - javaChannel().socket().shutdownOutput(); - } - } - - @Override - public ChannelFuture shutdownOutput() { - return shutdownOutput(newPromise()); - } - - @Override - public ChannelFuture shutdownOutput(final ChannelPromise promise) { - final EventLoop loop = eventLoop(); - if (loop.inEventLoop()) { - ((AbstractUnsafe) unsafe()).shutdownOutput(promise); - } else { - loop.execute( - new Runnable() { - @Override - public void run() { - ((AbstractUnsafe) unsafe()).shutdownOutput(promise); - } - }); - } - return promise; - } - - @Override - public ChannelFuture shutdownInput() { - return shutdownInput(newPromise()); - } - - @Override - protected boolean isInputShutdown0() { - return isInputShutdown(); - } - - @Override - public ChannelFuture shutdownInput(final ChannelPromise promise) { - EventLoop loop = eventLoop(); - if (loop.inEventLoop()) { - shutdownInput0(promise); - } else { - loop.execute( - new Runnable() { - @Override - public void run() { - shutdownInput0(promise); - } - }); - } - return promise; - } - - @Override - public ChannelFuture shutdown() { - return shutdown(newPromise()); - } - - @Override - public ChannelFuture shutdown(final ChannelPromise promise) { - ChannelFuture shutdownOutputFuture = shutdownOutput(); - if (shutdownOutputFuture.isDone()) { - shutdownOutputDone(shutdownOutputFuture, promise); - } else { - shutdownOutputFuture.addListener( - new ChannelFutureListener() { - @Override - public void operationComplete(final ChannelFuture shutdownOutputFuture) - throws Exception { - shutdownOutputDone(shutdownOutputFuture, promise); - } - }); - } - return promise; - } - - private void shutdownOutputDone( - final ChannelFuture shutdownOutputFuture, final ChannelPromise promise) { - ChannelFuture shutdownInputFuture = shutdownInput(); - if (shutdownInputFuture.isDone()) { - shutdownDone(shutdownOutputFuture, shutdownInputFuture, promise); - } else { - shutdownInputFuture.addListener( - new ChannelFutureListener() { - @Override - public void operationComplete(ChannelFuture shutdownInputFuture) throws Exception { - shutdownDone(shutdownOutputFuture, shutdownInputFuture, promise); - } - }); - } - } - - private static void shutdownDone( - ChannelFuture shutdownOutputFuture, - ChannelFuture shutdownInputFuture, - ChannelPromise promise) { - Throwable shutdownOutputCause = shutdownOutputFuture.cause(); - Throwable shutdownInputCause = shutdownInputFuture.cause(); - if (shutdownOutputCause != null) { - if (shutdownInputCause != null) { - logger.debug( - "Exception suppressed because a previous exception occurred.", shutdownInputCause); - } - promise.setFailure(shutdownOutputCause); - } else if (shutdownInputCause != null) { - promise.setFailure(shutdownInputCause); - } else { - promise.setSuccess(); - } - } - - private void shutdownInput0(final ChannelPromise promise) { - try { - shutdownInput0(); - promise.setSuccess(); - } catch (Throwable t) { - promise.setFailure(t); - } - } - - @SuppressJava6Requirement(reason = "Usage guarded by java version check") - private void shutdownInput0() throws Exception { - if (PlatformDependent.javaVersion() >= 7) { - javaChannel().shutdownInput(); - } else { - javaChannel().socket().shutdownInput(); - } - } - - @Override - protected SocketAddress localAddress0() { - return javaChannel().socket().getLocalSocketAddress(); - } - - @Override - protected SocketAddress remoteAddress0() { - return javaChannel().socket().getRemoteSocketAddress(); - } - - @Override - protected void doBind(SocketAddress localAddress) throws Exception { - doBind0(localAddress); - } - - private void doBind0(SocketAddress localAddress) throws Exception { - if (PlatformDependent.javaVersion() >= 7) { - SocketUtils.bind(javaChannel(), localAddress); - } else { - SocketUtils.bind(javaChannel().socket(), localAddress); - } - } - - @Override - protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) - throws Exception { - if (localAddress != null) { - doBind0(localAddress); - } - - boolean success = false; - try { - boolean connected = SocketUtils.connect(javaChannel(), remoteAddress); - if (!connected) { - selectionKey().interestOps(SelectionKey.OP_CONNECT); - } - success = true; - return connected; - } finally { - if (!success) { - doClose(); - } - } - } - - @Override - protected void doFinishConnect() throws Exception { - if (!javaChannel().finishConnect()) { - throw new Error(); - } - } - - @Override - protected void doDisconnect() throws Exception { - doClose(); - } - - @Override - protected void doClose() throws Exception { - super.doClose(); - javaChannel().close(); - } - - @Override - protected int doReadBytes(ByteBuf byteBuf) throws Exception { - final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle(); - int attemptedBytes = byteBuf.writableBytes(); - allocHandle.attemptedBytesRead(attemptedBytes); - Histogram.Timer socketReadTime = socketReadDuration.startTimer(); - SocketChannel sc = javaChannel(); - int localReadBytes = byteBuf.writeBytes(sc, allocHandle.attemptedBytesRead()); - socketReadTime.observeDuration(); - socketReadBytes.observe(localReadBytes); - socketReadLeftBytes.observe(attemptedBytes - localReadBytes); - return localReadBytes; - } - - @Override - protected int doWriteBytes(ByteBuf buf) throws Exception { - final int expectedWrittenBytes = buf.readableBytes(); - return buf.readBytes(javaChannel(), expectedWrittenBytes); - } - - @Override - protected long doWriteFileRegion(FileRegion region) throws Exception { - final long position = region.transferred(); - return region.transferTo(javaChannel(), position); - } - - private void adjustMaxBytesPerGatheringWrite( - int attempted, int written, int oldMaxBytesPerGatheringWrite) { - // By default we track the SO_SNDBUF when ever it is explicitly set. However some OSes may - // dynamically change - // SO_SNDBUF (and other characteristics that determine how much data can be written at once) so - // we should try - // make a best effort to adjust as OS behavior changes. - if (attempted == written) { - if (attempted << 1 > oldMaxBytesPerGatheringWrite) { - ((NioSocketChannelConfig) config).setMaxBytesPerGatheringWrite(attempted << 1); - } - } else if (attempted > MAX_BYTES_PER_GATHERING_WRITE_ATTEMPTED_LOW_THRESHOLD - && written < attempted >>> 1) { - ((NioSocketChannelConfig) config).setMaxBytesPerGatheringWrite(attempted >>> 1); - } - } - - @Override - protected void doWrite(ChannelOutboundBuffer in) throws Exception { - SocketChannel ch = javaChannel(); - int writeSpinCount = config().getWriteSpinCount(); - do { - if (in.isEmpty()) { - // All written so clear OP_WRITE - clearOpWrite(); - // Directly return here so incompleteWrite(...) is not called. - return; - } - - // Ensure the pending writes are made of ByteBufs only. - int maxBytesPerGatheringWrite = - ((NioSocketChannelConfig) config).getMaxBytesPerGatheringWrite(); - ByteBuffer[] nioBuffers = in.nioBuffers(1024, maxBytesPerGatheringWrite); - int nioBufferCnt = in.nioBufferCount(); - - // Always use nioBuffers() to workaround data-corruption. - // See https://github.com/netty/netty/issues/2761 - switch (nioBufferCnt) { - case 0: - // We have something else beside ByteBuffers to write so fallback to normal writes. - writeSpinCount -= doWrite0(in); - break; - case 1: - { - // Only one ByteBuf so use non-gathering write - // Zero length buffers are not added to nioBuffers by ChannelOutboundBuffer, so there is - // no need - // to check if the total size of all the buffers is non-zero. - ByteBuffer buffer = nioBuffers[0]; - int attemptedBytes = buffer.remaining(); - socketWriteBytes.observe(attemptedBytes); - Histogram.Timer writeTime = socketWriteDuration.startTimer(); - final int localWrittenBytes = ch.write(buffer); - writeTime.observeDuration(); - socketWrittenBytes.observe(localWrittenBytes); - if (localWrittenBytes <= 0) { - incompleteWrite(true); - return; - } - socketWriteLeftBytes.observe(attemptedBytes - localWrittenBytes); - adjustMaxBytesPerGatheringWrite( - attemptedBytes, localWrittenBytes, maxBytesPerGatheringWrite); - in.removeBytes(localWrittenBytes); - --writeSpinCount; - break; - } - default: - { - // Zero length buffers are not added to nioBuffers by ChannelOutboundBuffer, so there is - // no need - // to check if the total size of all the buffers is non-zero. - // We limit the max amount to int above so cast is safe - long attemptedBytes = in.nioBufferSize(); - socketWriteBytes.observe(attemptedBytes); - Histogram.Timer writeTime = socketWriteDuration.startTimer(); - final long localWrittenBytes = ch.write(nioBuffers, 0, nioBufferCnt); - writeTime.observeDuration(); - socketWrittenBytes.observe(localWrittenBytes); - if (localWrittenBytes <= 0) { - incompleteWrite(true); - return; - } - socketWriteLeftBytes.observe(attemptedBytes - localWrittenBytes); - // Casting to int is safe because we limit the total amount of data in the nioBuffers to - // int above. - adjustMaxBytesPerGatheringWrite( - (int) attemptedBytes, (int) localWrittenBytes, maxBytesPerGatheringWrite); - in.removeBytes(localWrittenBytes); - --writeSpinCount; - break; - } - } - } while (writeSpinCount > 0); - - incompleteWrite(writeSpinCount < 0); - } - - @Override - protected AbstractNioUnsafe newUnsafe() { - return new NioSocketChannelUnsafe(); - } - - private final class NioSocketChannelUnsafe extends NioByteUnsafe { - @Override - protected Executor prepareToClose() { - try { - if (javaChannel().isOpen() && config().getSoLinger() > 0) { - // We need to cancel this key of the channel so we may not end up in a eventloop spin - // because we try to read or write until the actual close happens which may be later due - // SO_LINGER handling. - // See https://github.com/netty/netty/issues/4449 - doDeregister(); - return GlobalEventExecutor.INSTANCE; - } - } catch (Throwable ignore) { - // Ignore the error as the underlying channel may be closed in the meantime and so - // getSoLinger() may produce an exception. In this case we just return null. - // See https://github.com/netty/netty/issues/4449 - } - return null; - } - } - - private final class NioSocketChannelConfig extends DefaultSocketChannelConfig { - private volatile int maxBytesPerGatheringWrite = Integer.MAX_VALUE; - - private NioSocketChannelConfig(NioSocketChannel channel, Socket javaSocket) { - super(channel, javaSocket); - calculateMaxBytesPerGatheringWrite(); - } - - @Override - protected void autoReadCleared() { - clearReadPending(); - } - - @Override - public NioSocketChannelConfig setSendBufferSize(int sendBufferSize) { - super.setSendBufferSize(sendBufferSize); - calculateMaxBytesPerGatheringWrite(); - return this; - } - - @Override - public boolean setOption(ChannelOption option, T value) { - if (PlatformDependent.javaVersion() >= 7 && option instanceof NioChannelOption) { - return NioChannelOption.setOption(jdkChannel(), (NioChannelOption) option, value); - } - return super.setOption(option, value); - } - - @Override - public T getOption(ChannelOption option) { - if (PlatformDependent.javaVersion() >= 7 && option instanceof NioChannelOption) { - return NioChannelOption.getOption(jdkChannel(), (NioChannelOption) option); - } - return super.getOption(option); - } - - @Override - public Map, Object> getOptions() { - if (PlatformDependent.javaVersion() >= 7) { - return getOptions(super.getOptions(), NioChannelOption.getOptions(jdkChannel())); - } - return super.getOptions(); - } - - void setMaxBytesPerGatheringWrite(int maxBytesPerGatheringWrite) { - this.maxBytesPerGatheringWrite = maxBytesPerGatheringWrite; - } - - int getMaxBytesPerGatheringWrite() { - return maxBytesPerGatheringWrite; - } - - private void calculateMaxBytesPerGatheringWrite() { - // Multiply by 2 to give some extra space in case the OS can process write data faster than we - // can provide. - int newSendBufferSize = getSendBufferSize() << 1; - if (newSendBufferSize > 0) { - setMaxBytesPerGatheringWrite(newSendBufferSize); - } - } - - private SocketChannel jdkChannel() { - return ((NioSocketChannel) channel).javaChannel(); - } - } -} diff --git a/src/main/java/io/netty/handler/codec/http2/DefaultHttp2FrameWriter.java b/src/main/java/io/netty/handler/codec/http2/DefaultHttp2FrameWriter.java deleted file mode 100644 index b19ec4a4a38..00000000000 --- a/src/main/java/io/netty/handler/codec/http2/DefaultHttp2FrameWriter.java +++ /dev/null @@ -1,719 +0,0 @@ -/* - * Copyright 2014 The Netty Project - * - * The Netty Project licenses this file to you under the Apache License, version 2.0 (the - * "License"); you may not use this file except in compliance with the License. You may obtain a - * copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, 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. - */ - -package io.netty.handler.codec.http2; - -import static io.netty.buffer.Unpooled.directBuffer; -import static io.netty.buffer.Unpooled.unreleasableBuffer; -import static io.netty.handler.codec.http2.Http2CodecUtil.CONTINUATION_FRAME_HEADER_LENGTH; -import static io.netty.handler.codec.http2.Http2CodecUtil.DATA_FRAME_HEADER_LENGTH; -import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_MAX_FRAME_SIZE; -import static io.netty.handler.codec.http2.Http2CodecUtil.FRAME_HEADER_LENGTH; -import static io.netty.handler.codec.http2.Http2CodecUtil.GO_AWAY_FRAME_HEADER_LENGTH; -import static io.netty.handler.codec.http2.Http2CodecUtil.HEADERS_FRAME_HEADER_LENGTH; -import static io.netty.handler.codec.http2.Http2CodecUtil.INT_FIELD_LENGTH; -import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_UNSIGNED_BYTE; -import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_UNSIGNED_INT; -import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_WEIGHT; -import static io.netty.handler.codec.http2.Http2CodecUtil.MIN_WEIGHT; -import static io.netty.handler.codec.http2.Http2CodecUtil.PING_FRAME_PAYLOAD_LENGTH; -import static io.netty.handler.codec.http2.Http2CodecUtil.PRIORITY_ENTRY_LENGTH; -import static io.netty.handler.codec.http2.Http2CodecUtil.PRIORITY_FRAME_LENGTH; -import static io.netty.handler.codec.http2.Http2CodecUtil.PUSH_PROMISE_FRAME_HEADER_LENGTH; -import static io.netty.handler.codec.http2.Http2CodecUtil.RST_STREAM_FRAME_LENGTH; -import static io.netty.handler.codec.http2.Http2CodecUtil.SETTING_ENTRY_LENGTH; -import static io.netty.handler.codec.http2.Http2CodecUtil.WINDOW_UPDATE_FRAME_LENGTH; -import static io.netty.handler.codec.http2.Http2CodecUtil.isMaxFrameSizeValid; -import static io.netty.handler.codec.http2.Http2CodecUtil.verifyPadding; -import static io.netty.handler.codec.http2.Http2CodecUtil.writeFrameHeaderInternal; -import static io.netty.handler.codec.http2.Http2Error.FRAME_SIZE_ERROR; -import static io.netty.handler.codec.http2.Http2Exception.connectionError; -import static io.netty.handler.codec.http2.Http2FrameTypes.CONTINUATION; -import static io.netty.handler.codec.http2.Http2FrameTypes.DATA; -import static io.netty.handler.codec.http2.Http2FrameTypes.GO_AWAY; -import static io.netty.handler.codec.http2.Http2FrameTypes.HEADERS; -import static io.netty.handler.codec.http2.Http2FrameTypes.PING; -import static io.netty.handler.codec.http2.Http2FrameTypes.PRIORITY; -import static io.netty.handler.codec.http2.Http2FrameTypes.PUSH_PROMISE; -import static io.netty.handler.codec.http2.Http2FrameTypes.RST_STREAM; -import static io.netty.handler.codec.http2.Http2FrameTypes.SETTINGS; -import static io.netty.handler.codec.http2.Http2FrameTypes.WINDOW_UPDATE; -import static io.netty.util.internal.ObjectUtil.checkNotNull; -import static io.netty.util.internal.ObjectUtil.checkPositive; -import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero; -import static java.lang.Math.max; -import static java.lang.Math.min; - -import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelPromise; -import io.netty.handler.codec.http2.Http2CodecUtil.SimpleChannelPromiseAggregator; -import io.netty.handler.codec.http2.Http2FrameWriter.Configuration; -import io.netty.handler.codec.http2.Http2HeadersEncoder.SensitivityDetector; -import io.netty.util.internal.PlatformDependent; -import io.netty.util.internal.UnstableApi; -import io.prometheus.client.Histogram; -import org.tikv.common.util.HistogramUtils; - -/** A {@link Http2FrameWriter} that supports all frame types defined by the HTTP/2 specification. */ -@UnstableApi -public class DefaultHttp2FrameWriter - implements Http2FrameWriter, Http2FrameSizePolicy, Configuration { - private static final String STREAM_ID = "Stream ID"; - private static final String STREAM_DEPENDENCY = "Stream Dependency"; - /** - * This buffer is allocated to the maximum size of the padding field, and filled with zeros. When - * padding is needed it can be taken as a slice of this buffer. Users should call {@link - * ByteBuf#retain()} before using their slice. - */ - private static final ByteBuf ZERO_BUFFER = - unreleasableBuffer(directBuffer(MAX_UNSIGNED_BYTE).writeZero(MAX_UNSIGNED_BYTE)).asReadOnly(); - - private final Http2HeadersEncoder headersEncoder; - private int maxFrameSize; - - public static final Histogram writeHeaderDuration = - HistogramUtils.buildDuration() - .name("netty_http2_frame_writer_write_header_duration_seconds") - .help("Time taken to encode a header") - .register(); - - public DefaultHttp2FrameWriter() { - this(new DefaultHttp2HeadersEncoder()); - } - - public DefaultHttp2FrameWriter(SensitivityDetector headersSensitivityDetector) { - this(new DefaultHttp2HeadersEncoder(headersSensitivityDetector)); - } - - public DefaultHttp2FrameWriter( - SensitivityDetector headersSensitivityDetector, boolean ignoreMaxHeaderListSize) { - this(new DefaultHttp2HeadersEncoder(headersSensitivityDetector, ignoreMaxHeaderListSize)); - } - - public DefaultHttp2FrameWriter(Http2HeadersEncoder headersEncoder) { - this.headersEncoder = headersEncoder; - maxFrameSize = DEFAULT_MAX_FRAME_SIZE; - } - - @Override - public Configuration configuration() { - return this; - } - - @Override - public Http2HeadersEncoder.Configuration headersConfiguration() { - return headersEncoder.configuration(); - } - - @Override - public Http2FrameSizePolicy frameSizePolicy() { - return this; - } - - @Override - public void maxFrameSize(int max) throws Http2Exception { - if (!isMaxFrameSizeValid(max)) { - throw connectionError( - FRAME_SIZE_ERROR, "Invalid MAX_FRAME_SIZE specified in sent settings: %d", max); - } - maxFrameSize = max; - } - - @Override - public int maxFrameSize() { - return maxFrameSize; - } - - @Override - public void close() {} - - @Override - public ChannelFuture writeData( - ChannelHandlerContext ctx, - int streamId, - ByteBuf data, - int padding, - boolean endStream, - ChannelPromise promise) { - final SimpleChannelPromiseAggregator promiseAggregator = - new SimpleChannelPromiseAggregator(promise, ctx.channel(), ctx.executor()); - ByteBuf frameHeader = null; - try { - verifyStreamId(streamId, STREAM_ID); - verifyPadding(padding); - - int remainingData = data.readableBytes(); - Http2Flags flags = new Http2Flags(); - flags.endOfStream(false); - flags.paddingPresent(false); - // Fast path to write frames of payload size maxFrameSize first. - if (remainingData > maxFrameSize) { - frameHeader = ctx.alloc().buffer(FRAME_HEADER_LENGTH); - writeFrameHeaderInternal(frameHeader, maxFrameSize, DATA, flags, streamId); - do { - // Write the header. - ctx.write(frameHeader.retainedSlice(), promiseAggregator.newPromise()); - - // Write the payload. - ctx.write(data.readRetainedSlice(maxFrameSize), promiseAggregator.newPromise()); - - remainingData -= maxFrameSize; - // Stop iterating if remainingData == maxFrameSize so we can take care of reference counts - // below. - } while (remainingData > maxFrameSize); - } - - if (padding == 0) { - // Write the header. - if (frameHeader != null) { - frameHeader.release(); - frameHeader = null; - } - ByteBuf frameHeader2 = ctx.alloc().buffer(FRAME_HEADER_LENGTH); - flags.endOfStream(endStream); - writeFrameHeaderInternal(frameHeader2, remainingData, DATA, flags, streamId); - ctx.write(frameHeader2, promiseAggregator.newPromise()); - - // Write the payload. - ByteBuf lastFrame = data.readSlice(remainingData); - data = null; - ctx.write(lastFrame, promiseAggregator.newPromise()); - } else { - if (remainingData != maxFrameSize) { - if (frameHeader != null) { - frameHeader.release(); - frameHeader = null; - } - } else { - remainingData -= maxFrameSize; - // Write the header. - ByteBuf lastFrame; - if (frameHeader == null) { - lastFrame = ctx.alloc().buffer(FRAME_HEADER_LENGTH); - writeFrameHeaderInternal(lastFrame, maxFrameSize, DATA, flags, streamId); - } else { - lastFrame = frameHeader.slice(); - frameHeader = null; - } - ctx.write(lastFrame, promiseAggregator.newPromise()); - - // Write the payload. - lastFrame = data.readableBytes() != maxFrameSize ? data.readSlice(maxFrameSize) : data; - data = null; - ctx.write(lastFrame, promiseAggregator.newPromise()); - } - - do { - int frameDataBytes = min(remainingData, maxFrameSize); - int framePaddingBytes = min(padding, max(0, (maxFrameSize - 1) - frameDataBytes)); - - // Decrement the remaining counters. - padding -= framePaddingBytes; - remainingData -= frameDataBytes; - - // Write the header. - ByteBuf frameHeader2 = ctx.alloc().buffer(DATA_FRAME_HEADER_LENGTH); - flags.endOfStream(endStream && remainingData == 0 && padding == 0); - flags.paddingPresent(framePaddingBytes > 0); - writeFrameHeaderInternal( - frameHeader2, framePaddingBytes + frameDataBytes, DATA, flags, streamId); - writePaddingLength(frameHeader2, framePaddingBytes); - ctx.write(frameHeader2, promiseAggregator.newPromise()); - - // Write the payload. - if (frameDataBytes != 0) { - if (remainingData == 0) { - ByteBuf lastFrame = data.readSlice(frameDataBytes); - data = null; - ctx.write(lastFrame, promiseAggregator.newPromise()); - } else { - ctx.write(data.readRetainedSlice(frameDataBytes), promiseAggregator.newPromise()); - } - } - // Write the frame padding. - if (paddingBytes(framePaddingBytes) > 0) { - ctx.write( - ZERO_BUFFER.slice(0, paddingBytes(framePaddingBytes)), - promiseAggregator.newPromise()); - } - } while (remainingData != 0 || padding != 0); - } - } catch (Throwable cause) { - if (frameHeader != null) { - frameHeader.release(); - } - // Use a try/finally here in case the data has been released before calling this method. This - // is not - // necessary above because we internally allocate frameHeader. - try { - if (data != null) { - data.release(); - } - } finally { - promiseAggregator.setFailure(cause); - promiseAggregator.doneAllocatingPromises(); - } - return promiseAggregator; - } - return promiseAggregator.doneAllocatingPromises(); - } - - @Override - public ChannelFuture writeHeaders( - ChannelHandlerContext ctx, - int streamId, - Http2Headers headers, - int padding, - boolean endStream, - ChannelPromise promise) { - return writeHeadersInternal( - ctx, streamId, headers, padding, endStream, false, 0, (short) 0, false, promise); - } - - @Override - public ChannelFuture writeHeaders( - ChannelHandlerContext ctx, - int streamId, - Http2Headers headers, - int streamDependency, - short weight, - boolean exclusive, - int padding, - boolean endStream, - ChannelPromise promise) { - return writeHeadersInternal( - ctx, - streamId, - headers, - padding, - endStream, - true, - streamDependency, - weight, - exclusive, - promise); - } - - @Override - public ChannelFuture writePriority( - ChannelHandlerContext ctx, - int streamId, - int streamDependency, - short weight, - boolean exclusive, - ChannelPromise promise) { - try { - verifyStreamId(streamId, STREAM_ID); - verifyStreamOrConnectionId(streamDependency, STREAM_DEPENDENCY); - verifyWeight(weight); - - ByteBuf buf = ctx.alloc().buffer(PRIORITY_FRAME_LENGTH); - writeFrameHeaderInternal(buf, PRIORITY_ENTRY_LENGTH, PRIORITY, new Http2Flags(), streamId); - buf.writeInt(exclusive ? (int) (0x80000000L | streamDependency) : streamDependency); - // Adjust the weight so that it fits into a single byte on the wire. - buf.writeByte(weight - 1); - return ctx.write(buf, promise); - } catch (Throwable t) { - return promise.setFailure(t); - } - } - - @Override - public ChannelFuture writeRstStream( - ChannelHandlerContext ctx, int streamId, long errorCode, ChannelPromise promise) { - try { - verifyStreamId(streamId, STREAM_ID); - verifyErrorCode(errorCode); - - ByteBuf buf = ctx.alloc().buffer(RST_STREAM_FRAME_LENGTH); - writeFrameHeaderInternal(buf, INT_FIELD_LENGTH, RST_STREAM, new Http2Flags(), streamId); - buf.writeInt((int) errorCode); - return ctx.write(buf, promise); - } catch (Throwable t) { - return promise.setFailure(t); - } - } - - @Override - public ChannelFuture writeSettings( - ChannelHandlerContext ctx, Http2Settings settings, ChannelPromise promise) { - try { - checkNotNull(settings, "settings"); - int payloadLength = SETTING_ENTRY_LENGTH * settings.size(); - ByteBuf buf = - ctx.alloc().buffer(FRAME_HEADER_LENGTH + settings.size() * SETTING_ENTRY_LENGTH); - writeFrameHeaderInternal(buf, payloadLength, SETTINGS, new Http2Flags(), 0); - for (Http2Settings.PrimitiveEntry entry : settings.entries()) { - buf.writeChar(entry.key()); - buf.writeInt(entry.value().intValue()); - } - return ctx.write(buf, promise); - } catch (Throwable t) { - return promise.setFailure(t); - } - } - - @Override - public ChannelFuture writeSettingsAck(ChannelHandlerContext ctx, ChannelPromise promise) { - try { - ByteBuf buf = ctx.alloc().buffer(FRAME_HEADER_LENGTH); - writeFrameHeaderInternal(buf, 0, SETTINGS, new Http2Flags().ack(true), 0); - return ctx.write(buf, promise); - } catch (Throwable t) { - return promise.setFailure(t); - } - } - - @Override - public ChannelFuture writePing( - ChannelHandlerContext ctx, boolean ack, long data, ChannelPromise promise) { - Http2Flags flags = ack ? new Http2Flags().ack(true) : new Http2Flags(); - ByteBuf buf = ctx.alloc().buffer(FRAME_HEADER_LENGTH + PING_FRAME_PAYLOAD_LENGTH); - // Assume nothing below will throw until buf is written. That way we don't have to take care of - // ownership - // in the catch block. - writeFrameHeaderInternal(buf, PING_FRAME_PAYLOAD_LENGTH, PING, flags, 0); - buf.writeLong(data); - return ctx.write(buf, promise); - } - - @Override - public ChannelFuture writePushPromise( - ChannelHandlerContext ctx, - int streamId, - int promisedStreamId, - Http2Headers headers, - int padding, - ChannelPromise promise) { - ByteBuf headerBlock = null; - SimpleChannelPromiseAggregator promiseAggregator = - new SimpleChannelPromiseAggregator(promise, ctx.channel(), ctx.executor()); - try { - verifyStreamId(streamId, STREAM_ID); - verifyStreamId(promisedStreamId, "Promised Stream ID"); - verifyPadding(padding); - - // Encode the entire header block into an intermediate buffer. - headerBlock = ctx.alloc().buffer(); - headersEncoder.encodeHeaders(streamId, headers, headerBlock); - - // Read the first fragment (possibly everything). - Http2Flags flags = new Http2Flags().paddingPresent(padding > 0); - // INT_FIELD_LENGTH is for the length of the promisedStreamId - int nonFragmentLength = INT_FIELD_LENGTH + padding; - int maxFragmentLength = maxFrameSize - nonFragmentLength; - ByteBuf fragment = - headerBlock.readRetainedSlice(min(headerBlock.readableBytes(), maxFragmentLength)); - - flags.endOfHeaders(!headerBlock.isReadable()); - - int payloadLength = fragment.readableBytes() + nonFragmentLength; - ByteBuf buf = ctx.alloc().buffer(PUSH_PROMISE_FRAME_HEADER_LENGTH); - writeFrameHeaderInternal(buf, payloadLength, PUSH_PROMISE, flags, streamId); - writePaddingLength(buf, padding); - - // Write out the promised stream ID. - buf.writeInt(promisedStreamId); - ctx.write(buf, promiseAggregator.newPromise()); - - // Write the first fragment. - ctx.write(fragment, promiseAggregator.newPromise()); - - // Write out the padding, if any. - if (paddingBytes(padding) > 0) { - ctx.write(ZERO_BUFFER.slice(0, paddingBytes(padding)), promiseAggregator.newPromise()); - } - - if (!flags.endOfHeaders()) { - writeContinuationFrames(ctx, streamId, headerBlock, promiseAggregator); - } - } catch (Http2Exception e) { - promiseAggregator.setFailure(e); - } catch (Throwable t) { - promiseAggregator.setFailure(t); - promiseAggregator.doneAllocatingPromises(); - PlatformDependent.throwException(t); - } finally { - if (headerBlock != null) { - headerBlock.release(); - } - } - return promiseAggregator.doneAllocatingPromises(); - } - - @Override - public ChannelFuture writeGoAway( - ChannelHandlerContext ctx, - int lastStreamId, - long errorCode, - ByteBuf debugData, - ChannelPromise promise) { - SimpleChannelPromiseAggregator promiseAggregator = - new SimpleChannelPromiseAggregator(promise, ctx.channel(), ctx.executor()); - try { - verifyStreamOrConnectionId(lastStreamId, "Last Stream ID"); - verifyErrorCode(errorCode); - - int payloadLength = 8 + debugData.readableBytes(); - ByteBuf buf = ctx.alloc().buffer(GO_AWAY_FRAME_HEADER_LENGTH); - // Assume nothing below will throw until buf is written. That way we don't have to take care - // of ownership - // in the catch block. - writeFrameHeaderInternal(buf, payloadLength, GO_AWAY, new Http2Flags(), 0); - buf.writeInt(lastStreamId); - buf.writeInt((int) errorCode); - ctx.write(buf, promiseAggregator.newPromise()); - } catch (Throwable t) { - try { - debugData.release(); - } finally { - promiseAggregator.setFailure(t); - promiseAggregator.doneAllocatingPromises(); - } - return promiseAggregator; - } - - try { - ctx.write(debugData, promiseAggregator.newPromise()); - } catch (Throwable t) { - promiseAggregator.setFailure(t); - } - return promiseAggregator.doneAllocatingPromises(); - } - - @Override - public ChannelFuture writeWindowUpdate( - ChannelHandlerContext ctx, int streamId, int windowSizeIncrement, ChannelPromise promise) { - try { - verifyStreamOrConnectionId(streamId, STREAM_ID); - verifyWindowSizeIncrement(windowSizeIncrement); - - ByteBuf buf = ctx.alloc().buffer(WINDOW_UPDATE_FRAME_LENGTH); - writeFrameHeaderInternal(buf, INT_FIELD_LENGTH, WINDOW_UPDATE, new Http2Flags(), streamId); - buf.writeInt(windowSizeIncrement); - return ctx.write(buf, promise); - } catch (Throwable t) { - return promise.setFailure(t); - } - } - - @Override - public ChannelFuture writeFrame( - ChannelHandlerContext ctx, - byte frameType, - int streamId, - Http2Flags flags, - ByteBuf payload, - ChannelPromise promise) { - SimpleChannelPromiseAggregator promiseAggregator = - new SimpleChannelPromiseAggregator(promise, ctx.channel(), ctx.executor()); - try { - verifyStreamOrConnectionId(streamId, STREAM_ID); - ByteBuf buf = ctx.alloc().buffer(FRAME_HEADER_LENGTH); - // Assume nothing below will throw until buf is written. That way we don't have to take care - // of ownership - // in the catch block. - writeFrameHeaderInternal(buf, payload.readableBytes(), frameType, flags, streamId); - ctx.write(buf, promiseAggregator.newPromise()); - } catch (Throwable t) { - try { - payload.release(); - } finally { - promiseAggregator.setFailure(t); - promiseAggregator.doneAllocatingPromises(); - } - return promiseAggregator; - } - try { - ctx.write(payload, promiseAggregator.newPromise()); - } catch (Throwable t) { - promiseAggregator.setFailure(t); - } - return promiseAggregator.doneAllocatingPromises(); - } - - private ChannelFuture writeHeadersInternal( - ChannelHandlerContext ctx, - int streamId, - Http2Headers headers, - int padding, - boolean endStream, - boolean hasPriority, - int streamDependency, - short weight, - boolean exclusive, - ChannelPromise promise) { - Histogram.Timer writeHeaderTimer = writeHeaderDuration.startTimer(); - ByteBuf headerBlock = null; - SimpleChannelPromiseAggregator promiseAggregator = - new SimpleChannelPromiseAggregator(promise, ctx.channel(), ctx.executor()); - try { - verifyStreamId(streamId, STREAM_ID); - if (hasPriority) { - verifyStreamOrConnectionId(streamDependency, STREAM_DEPENDENCY); - verifyPadding(padding); - verifyWeight(weight); - } - - // Encode the entire header block. - headerBlock = ctx.alloc().buffer(); - headersEncoder.encodeHeaders(streamId, headers, headerBlock); - - Http2Flags flags = - new Http2Flags() - .endOfStream(endStream) - .priorityPresent(hasPriority) - .paddingPresent(padding > 0); - - // Read the first fragment (possibly everything). - int nonFragmentBytes = padding + flags.getNumPriorityBytes(); - int maxFragmentLength = maxFrameSize - nonFragmentBytes; - ByteBuf fragment = - headerBlock.readRetainedSlice(min(headerBlock.readableBytes(), maxFragmentLength)); - - // Set the end of headers flag for the first frame. - flags.endOfHeaders(!headerBlock.isReadable()); - - int payloadLength = fragment.readableBytes() + nonFragmentBytes; - ByteBuf buf = ctx.alloc().buffer(HEADERS_FRAME_HEADER_LENGTH); - writeFrameHeaderInternal(buf, payloadLength, HEADERS, flags, streamId); - writePaddingLength(buf, padding); - - if (hasPriority) { - buf.writeInt(exclusive ? (int) (0x80000000L | streamDependency) : streamDependency); - - // Adjust the weight so that it fits into a single byte on the wire. - buf.writeByte(weight - 1); - } - ctx.write(buf, promiseAggregator.newPromise()); - - // Write the first fragment. - ctx.write(fragment, promiseAggregator.newPromise()); - - // Write out the padding, if any. - if (paddingBytes(padding) > 0) { - ctx.write(ZERO_BUFFER.slice(0, paddingBytes(padding)), promiseAggregator.newPromise()); - } - - if (!flags.endOfHeaders()) { - writeContinuationFrames(ctx, streamId, headerBlock, promiseAggregator); - } - } catch (Http2Exception e) { - promiseAggregator.setFailure(e); - } catch (Throwable t) { - promiseAggregator.setFailure(t); - promiseAggregator.doneAllocatingPromises(); - PlatformDependent.throwException(t); - } finally { - if (headerBlock != null) { - headerBlock.release(); - } - } - ChannelPromise result = promiseAggregator.doneAllocatingPromises(); - writeHeaderTimer.observeDuration(); - return result; - } - - /** - * Writes as many continuation frames as needed until {@code padding} and {@code headerBlock} are - * consumed. - */ - private ChannelFuture writeContinuationFrames( - ChannelHandlerContext ctx, - int streamId, - ByteBuf headerBlock, - SimpleChannelPromiseAggregator promiseAggregator) { - Http2Flags flags = new Http2Flags(); - - if (headerBlock.isReadable()) { - // The frame header (and padding) only changes on the last frame, so allocate it once and - // re-use - int fragmentReadableBytes = min(headerBlock.readableBytes(), maxFrameSize); - ByteBuf buf = ctx.alloc().buffer(CONTINUATION_FRAME_HEADER_LENGTH); - writeFrameHeaderInternal(buf, fragmentReadableBytes, CONTINUATION, flags, streamId); - - do { - fragmentReadableBytes = min(headerBlock.readableBytes(), maxFrameSize); - ByteBuf fragment = headerBlock.readRetainedSlice(fragmentReadableBytes); - - if (headerBlock.isReadable()) { - ctx.write(buf.retain(), promiseAggregator.newPromise()); - } else { - // The frame header is different for the last frame, so re-allocate and release the old - // buffer - flags = flags.endOfHeaders(true); - buf.release(); - buf = ctx.alloc().buffer(CONTINUATION_FRAME_HEADER_LENGTH); - writeFrameHeaderInternal(buf, fragmentReadableBytes, CONTINUATION, flags, streamId); - ctx.write(buf, promiseAggregator.newPromise()); - } - - ctx.write(fragment, promiseAggregator.newPromise()); - - } while (headerBlock.isReadable()); - } - return promiseAggregator; - } - - /** Returns the number of padding bytes that should be appended to the end of a frame. */ - private static int paddingBytes(int padding) { - // The padding parameter contains the 1 byte pad length field as well as the trailing padding - // bytes. - // Subtract 1, so to only get the number of padding bytes that need to be appended to the end of - // a frame. - return padding - 1; - } - - private static void writePaddingLength(ByteBuf buf, int padding) { - if (padding > 0) { - // It is assumed that the padding length has been bounds checked before this - // Minus 1, as the pad length field is included in the padding parameter and is 1 byte wide. - buf.writeByte(padding - 1); - } - } - - private static void verifyStreamId(int streamId, String argumentName) { - checkPositive(streamId, argumentName); - } - - private static void verifyStreamOrConnectionId(int streamId, String argumentName) { - checkPositiveOrZero(streamId, argumentName); - } - - private static void verifyWeight(short weight) { - if (weight < MIN_WEIGHT || weight > MAX_WEIGHT) { - throw new IllegalArgumentException("Invalid weight: " + weight); - } - } - - private static void verifyErrorCode(long errorCode) { - if (errorCode < 0 || errorCode > MAX_UNSIGNED_INT) { - throw new IllegalArgumentException("Invalid errorCode: " + errorCode); - } - } - - private static void verifyWindowSizeIncrement(int windowSizeIncrement) { - checkPositiveOrZero(windowSizeIncrement, "windowSizeIncrement"); - } - - private static void verifyPingPayload(ByteBuf data) { - if (data == null || data.readableBytes() != PING_FRAME_PAYLOAD_LENGTH) { - throw new IllegalArgumentException( - "Opaque data must be " + PING_FRAME_PAYLOAD_LENGTH + " bytes"); - } - } -} diff --git a/src/main/java/io/netty/handler/codec/http2/DefaultHttp2RemoteFlowController.java b/src/main/java/io/netty/handler/codec/http2/DefaultHttp2RemoteFlowController.java deleted file mode 100644 index 469e0c63894..00000000000 --- a/src/main/java/io/netty/handler/codec/http2/DefaultHttp2RemoteFlowController.java +++ /dev/null @@ -1,788 +0,0 @@ -/* - * Copyright 2014 The Netty Project - * - * The Netty Project licenses this file to you under the Apache License, version 2.0 (the - * "License"); you may not use this file except in compliance with the License. You may obtain a - * copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, 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. - */ -package io.netty.handler.codec.http2; - -import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_WINDOW_SIZE; -import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_WEIGHT; -import static io.netty.handler.codec.http2.Http2CodecUtil.MIN_WEIGHT; -import static io.netty.handler.codec.http2.Http2Error.FLOW_CONTROL_ERROR; -import static io.netty.handler.codec.http2.Http2Error.INTERNAL_ERROR; -import static io.netty.handler.codec.http2.Http2Error.STREAM_CLOSED; -import static io.netty.handler.codec.http2.Http2Exception.streamError; -import static io.netty.handler.codec.http2.Http2Stream.State.HALF_CLOSED_LOCAL; -import static io.netty.util.internal.ObjectUtil.checkNotNull; -import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero; -import static java.lang.Math.max; -import static java.lang.Math.min; - -import io.netty.channel.ChannelHandlerContext; -import io.netty.util.internal.UnstableApi; -import io.netty.util.internal.logging.InternalLogger; -import io.netty.util.internal.logging.InternalLoggerFactory; -import io.prometheus.client.Histogram; -import java.util.ArrayDeque; -import java.util.Deque; -import org.tikv.common.util.HistogramUtils; - -/** - * Basic implementation of {@link Http2RemoteFlowController}. - * - *

This class is NOT thread safe. The assumption is all methods must be invoked - * from a single thread. Typically this thread is the event loop thread for the {@link - * ChannelHandlerContext} managed by this class. - */ -@UnstableApi -public class DefaultHttp2RemoteFlowController implements Http2RemoteFlowController { - private static final InternalLogger logger = - InternalLoggerFactory.getInstance(DefaultHttp2RemoteFlowController.class); - private static final int MIN_WRITABLE_CHUNK = 32 * 1024; - private final Http2Connection connection; - private final Http2Connection.PropertyKey stateKey; - private final StreamByteDistributor streamByteDistributor; - private final FlowState connectionState; - private int initialWindowSize = DEFAULT_WINDOW_SIZE; - private WritabilityMonitor monitor; - private ChannelHandlerContext ctx; - - public static final Histogram byteDistributedDuration = - HistogramUtils.buildDuration() - .name("netty_http2_byte_distributed_duration_seconds") - .help("The duration of byte distributed to streams.") - .register(); - - public DefaultHttp2RemoteFlowController(Http2Connection connection) { - this(connection, (Listener) null); - } - - public DefaultHttp2RemoteFlowController( - Http2Connection connection, StreamByteDistributor streamByteDistributor) { - this(connection, streamByteDistributor, null); - } - - public DefaultHttp2RemoteFlowController(Http2Connection connection, final Listener listener) { - this(connection, new WeightedFairQueueByteDistributor(connection), listener); - } - - public DefaultHttp2RemoteFlowController( - Http2Connection connection, - StreamByteDistributor streamByteDistributor, - final Listener listener) { - this.connection = checkNotNull(connection, "connection"); - this.streamByteDistributor = checkNotNull(streamByteDistributor, "streamWriteDistributor"); - - // Add a flow state for the connection. - stateKey = connection.newKey(); - connectionState = new FlowState(connection.connectionStream()); - connection.connectionStream().setProperty(stateKey, connectionState); - - // Monitor may depend upon connectionState, and so initialize after connectionState - listener(listener); - monitor.windowSize(connectionState, initialWindowSize); - - // Register for notification of new streams. - connection.addListener( - new Http2ConnectionAdapter() { - @Override - public void onStreamAdded(Http2Stream stream) { - // If the stream state is not open then the stream is not yet eligible for flow - // controlled frames and - // only requires the ReducedFlowState. Otherwise the full amount of memory is required. - stream.setProperty(stateKey, new FlowState(stream)); - } - - @Override - public void onStreamActive(Http2Stream stream) { - // If the object was previously created, but later activated then we have to ensure the - // proper - // initialWindowSize is used. - monitor.windowSize(state(stream), initialWindowSize); - } - - @Override - public void onStreamClosed(Http2Stream stream) { - // Any pending frames can never be written, cancel and - // write errors for any pending frames. - state(stream).cancel(STREAM_CLOSED, null); - } - - @Override - public void onStreamHalfClosed(Http2Stream stream) { - if (HALF_CLOSED_LOCAL == stream.state()) { - /** - * When this method is called there should not be any pending frames left if the API - * is used correctly. However, it is possible that a erroneous application can sneak - * in a frame even after having already written a frame with the END_STREAM flag set, - * as the stream state might not transition immediately to HALF_CLOSED_LOCAL / CLOSED - * due to flow control delaying the write. - * - *

This is to cancel any such illegal writes. - */ - state(stream).cancel(STREAM_CLOSED, null); - } - } - }); - } - - /** - * {@inheritDoc} - * - *

Any queued {@link FlowControlled} objects will be sent. - */ - @Override - public void channelHandlerContext(ChannelHandlerContext ctx) throws Http2Exception { - this.ctx = checkNotNull(ctx, "ctx"); - - // Writing the pending bytes will not check writability change and instead a writability change - // notification - // to be provided by an explicit call. - channelWritabilityChanged(); - - // Don't worry about cleaning up queued frames here if ctx is null. It is expected that all - // streams will be - // closed and the queue cleanup will occur when the stream state transitions occur. - - // If any frames have been queued up, we should send them now that we have a channel context. - if (isChannelWritable()) { - writePendingBytes(); - } - } - - @Override - public ChannelHandlerContext channelHandlerContext() { - return ctx; - } - - @Override - public void initialWindowSize(int newWindowSize) throws Http2Exception { - assert ctx == null || ctx.executor().inEventLoop(); - monitor.initialWindowSize(newWindowSize); - } - - @Override - public int initialWindowSize() { - return initialWindowSize; - } - - @Override - public int windowSize(Http2Stream stream) { - return state(stream).windowSize(); - } - - @Override - public boolean isWritable(Http2Stream stream) { - return monitor.isWritable(state(stream)); - } - - @Override - public void channelWritabilityChanged() throws Http2Exception { - monitor.channelWritabilityChange(); - } - - @Override - public void updateDependencyTree( - int childStreamId, int parentStreamId, short weight, boolean exclusive) { - // It is assumed there are all validated at a higher level. For example in the Http2FrameReader. - assert weight >= MIN_WEIGHT && weight <= MAX_WEIGHT : "Invalid weight"; - assert childStreamId != parentStreamId : "A stream cannot depend on itself"; - assert childStreamId > 0 && parentStreamId >= 0 - : "childStreamId must be > 0. parentStreamId must be >= 0."; - - streamByteDistributor.updateDependencyTree(childStreamId, parentStreamId, weight, exclusive); - } - - private boolean isChannelWritable() { - return ctx != null && isChannelWritable0(); - } - - private boolean isChannelWritable0() { - return ctx.channel().isWritable(); - } - - @Override - public void listener(Listener listener) { - monitor = - listener == null ? new WritabilityMonitor() : new ListenerWritabilityMonitor(listener); - } - - @Override - public void incrementWindowSize(Http2Stream stream, int delta) throws Http2Exception { - assert ctx == null || ctx.executor().inEventLoop(); - monitor.incrementWindowSize(state(stream), delta); - } - - @Override - public void addFlowControlled(Http2Stream stream, FlowControlled frame) { - // The context can be null assuming the frame will be queued and send later when the context is - // set. - assert ctx == null || ctx.executor().inEventLoop(); - checkNotNull(frame, "frame"); - try { - monitor.enqueueFrame(state(stream), frame); - } catch (Throwable t) { - frame.error(ctx, t); - } - } - - @Override - public boolean hasFlowControlled(Http2Stream stream) { - return state(stream).hasFrame(); - } - - private FlowState state(Http2Stream stream) { - return (FlowState) stream.getProperty(stateKey); - } - - /** Returns the flow control window for the entire connection. */ - private int connectionWindowSize() { - return connectionState.windowSize(); - } - - private int minUsableChannelBytes() { - // The current allocation algorithm values "fairness" and doesn't give any consideration to - // "goodput". It - // is possible that 1 byte will be allocated to many streams. In an effort to try to make - // "goodput" - // reasonable with the current allocation algorithm we have this "cheap" check up front to - // ensure there is - // an "adequate" amount of connection window before allocation is attempted. This is not - // foolproof as if the - // number of streams is >= this minimal number then we may still have the issue, but the idea is - // to narrow the - // circumstances in which this can happen without rewriting the allocation algorithm. - return max(ctx.channel().config().getWriteBufferLowWaterMark(), MIN_WRITABLE_CHUNK); - } - - private int maxUsableChannelBytes() { - // If the channel isWritable, allow at least minUsableChannelBytes. - int channelWritableBytes = (int) min(Integer.MAX_VALUE, ctx.channel().bytesBeforeUnwritable()); - int usableBytes = - channelWritableBytes > 0 ? max(channelWritableBytes, minUsableChannelBytes()) : 0; - - // Clip the usable bytes by the connection window. - return min(connectionState.windowSize(), usableBytes); - } - - /** - * The amount of bytes that can be supported by underlying {@link io.netty.channel.Channel} - * without queuing "too-much". - */ - private int writableBytes() { - return min(connectionWindowSize(), maxUsableChannelBytes()); - } - - @Override - public void writePendingBytes() throws Http2Exception { - monitor.writePendingBytes(); - } - - /** The remote flow control state for a single stream. */ - private final class FlowState implements StreamByteDistributor.StreamState { - private final Http2Stream stream; - private final Deque pendingWriteQueue; - private int window; - private long pendingBytes; - private boolean markedWritable; - - /** Set to true while a frame is being written, false otherwise. */ - private boolean writing; - /** Set to true if cancel() was called. */ - private boolean cancelled; - - FlowState(Http2Stream stream) { - this.stream = stream; - pendingWriteQueue = new ArrayDeque(2); - } - - /** - * Determine if the stream associated with this object is writable. - * - * @return {@code true} if the stream associated with this object is writable. - */ - boolean isWritable() { - return windowSize() > pendingBytes() && !cancelled; - } - - /** The stream this state is associated with. */ - @Override - public Http2Stream stream() { - return stream; - } - - /** Returns the parameter from the last call to {@link #markedWritability(boolean)}. */ - boolean markedWritability() { - return markedWritable; - } - - /** Save the state of writability. */ - void markedWritability(boolean isWritable) { - this.markedWritable = isWritable; - } - - @Override - public int windowSize() { - return window; - } - - /** Reset the window size for this stream. */ - void windowSize(int initialWindowSize) { - window = initialWindowSize; - } - - /** - * Write the allocated bytes for this stream. - * - * @return the number of bytes written for a stream or {@code -1} if no write occurred. - */ - int writeAllocatedBytes(int allocated) { - final int initialAllocated = allocated; - int writtenBytes; - // In case an exception is thrown we want to remember it and pass it to cancel(Throwable). - Throwable cause = null; - FlowControlled frame; - try { - assert !writing; - writing = true; - - // Write the remainder of frames that we are allowed to - boolean writeOccurred = false; - while (!cancelled && (frame = peek()) != null) { - int maxBytes = min(allocated, writableWindow()); - if (maxBytes <= 0 && frame.size() > 0) { - // The frame still has data, but the amount of allocated bytes has been exhausted. - // Don't write needless empty frames. - break; - } - writeOccurred = true; - int initialFrameSize = frame.size(); - try { - frame.write(ctx, max(0, maxBytes)); - if (frame.size() == 0) { - // This frame has been fully written, remove this frame and notify it. - // Since we remove this frame first, we're guaranteed that its error - // method will not be called when we call cancel. - pendingWriteQueue.remove(); - frame.writeComplete(); - } - } finally { - // Decrement allocated by how much was actually written. - allocated -= initialFrameSize - frame.size(); - } - } - - if (!writeOccurred) { - // Either there was no frame, or the amount of allocated bytes has been exhausted. - return -1; - } - - } catch (Throwable t) { - // Mark the state as cancelled, we'll clear the pending queue via cancel() below. - cancelled = true; - cause = t; - } finally { - writing = false; - // Make sure we always decrement the flow control windows - // by the bytes written. - writtenBytes = initialAllocated - allocated; - - decrementPendingBytes(writtenBytes, false); - decrementFlowControlWindow(writtenBytes); - - // If a cancellation occurred while writing, call cancel again to - // clear and error all of the pending writes. - if (cancelled) { - cancel(INTERNAL_ERROR, cause); - } - } - return writtenBytes; - } - - /** - * Increments the flow control window for this stream by the given delta and returns the new - * value. - */ - int incrementStreamWindow(int delta) throws Http2Exception { - if (delta > 0 && Integer.MAX_VALUE - delta < window) { - throw streamError( - stream.id(), FLOW_CONTROL_ERROR, "Window size overflow for stream: %d", stream.id()); - } - window += delta; - - streamByteDistributor.updateStreamableBytes(this); - return window; - } - - /** Returns the maximum writable window (minimum of the stream and connection windows). */ - private int writableWindow() { - return min(window, connectionWindowSize()); - } - - @Override - public long pendingBytes() { - return pendingBytes; - } - - /** Adds the {@code frame} to the pending queue and increments the pending byte count. */ - void enqueueFrame(FlowControlled frame) { - FlowControlled last = pendingWriteQueue.peekLast(); - if (last == null) { - enqueueFrameWithoutMerge(frame); - return; - } - - int lastSize = last.size(); - if (last.merge(ctx, frame)) { - incrementPendingBytes(last.size() - lastSize, true); - return; - } - enqueueFrameWithoutMerge(frame); - } - - private void enqueueFrameWithoutMerge(FlowControlled frame) { - pendingWriteQueue.offer(frame); - // This must be called after adding to the queue in order so that hasFrame() is - // updated before updating the stream state. - incrementPendingBytes(frame.size(), true); - } - - @Override - public boolean hasFrame() { - return !pendingWriteQueue.isEmpty(); - } - - /** Returns the head of the pending queue, or {@code null} if empty. */ - private FlowControlled peek() { - return pendingWriteQueue.peek(); - } - - /** - * Clears the pending queue and writes errors for each remaining frame. - * - * @param error the {@link Http2Error} to use. - * @param cause the {@link Throwable} that caused this method to be invoked. - */ - void cancel(Http2Error error, Throwable cause) { - cancelled = true; - // Ensure that the queue can't be modified while we are writing. - if (writing) { - return; - } - - FlowControlled frame = pendingWriteQueue.poll(); - if (frame != null) { - // Only create exception once and reuse to reduce overhead of filling in the stacktrace. - final Http2Exception exception = - streamError(stream.id(), error, cause, "Stream closed before write could take place"); - do { - writeError(frame, exception); - frame = pendingWriteQueue.poll(); - } while (frame != null); - } - - streamByteDistributor.updateStreamableBytes(this); - - monitor.stateCancelled(this); - } - - /** - * Increments the number of pending bytes for this node and optionally updates the {@link - * StreamByteDistributor}. - */ - private void incrementPendingBytes(int numBytes, boolean updateStreamableBytes) { - pendingBytes += numBytes; - monitor.incrementPendingBytes(numBytes); - if (updateStreamableBytes) { - streamByteDistributor.updateStreamableBytes(this); - } - } - - /** - * If this frame is in the pending queue, decrements the number of pending bytes for the stream. - */ - private void decrementPendingBytes(int bytes, boolean updateStreamableBytes) { - incrementPendingBytes(-bytes, updateStreamableBytes); - } - - /** Decrement the per stream and connection flow control window by {@code bytes}. */ - private void decrementFlowControlWindow(int bytes) { - try { - int negativeBytes = -bytes; - connectionState.incrementStreamWindow(negativeBytes); - incrementStreamWindow(negativeBytes); - } catch (Http2Exception e) { - // Should never get here since we're decrementing. - throw new IllegalStateException( - "Invalid window state when writing frame: " + e.getMessage(), e); - } - } - - /** - * Discards this {@link FlowControlled}, writing an error. If this frame is in the pending - * queue, the unwritten bytes are removed from this branch of the priority tree. - */ - private void writeError(FlowControlled frame, Http2Exception cause) { - assert ctx != null; - decrementPendingBytes(frame.size(), true); - frame.error(ctx, cause); - } - } - - /** Abstract class which provides common functionality for writability monitor implementations. */ - private class WritabilityMonitor implements StreamByteDistributor.Writer { - private boolean inWritePendingBytes; - private long totalPendingBytes; - - @Override - public final void write(Http2Stream stream, int numBytes) { - state(stream).writeAllocatedBytes(numBytes); - } - - /** - * Called when the writability of the underlying channel changes. - * - * @throws Http2Exception If a write occurs and an exception happens in the write operation. - */ - void channelWritabilityChange() throws Http2Exception {} - - /** - * Called when the state is cancelled. - * - * @param state the state that was cancelled. - */ - void stateCancelled(FlowState state) {} - - /** - * Set the initial window size for {@code state}. - * - * @param state the state to change the initial window size for. - * @param initialWindowSize the size of the window in bytes. - */ - void windowSize(FlowState state, int initialWindowSize) { - state.windowSize(initialWindowSize); - } - - /** - * Increment the window size for a particular stream. - * - * @param state the state associated with the stream whose window is being incremented. - * @param delta The amount to increment by. - * @throws Http2Exception If this operation overflows the window for {@code state}. - */ - void incrementWindowSize(FlowState state, int delta) throws Http2Exception { - state.incrementStreamWindow(delta); - } - - /** - * Add a frame to be sent via flow control. - * - * @param state The state associated with the stream which the {@code frame} is associated with. - * @param frame the frame to enqueue. - * @throws Http2Exception If a writability error occurs. - */ - void enqueueFrame(FlowState state, FlowControlled frame) throws Http2Exception { - state.enqueueFrame(frame); - } - - /** - * Increment the total amount of pending bytes for all streams. When any stream's pending bytes - * changes method should be called. - * - * @param delta The amount to increment by. - */ - final void incrementPendingBytes(int delta) { - totalPendingBytes += delta; - - // Notification of writibilty change should be delayed until the end of the top level event. - // This is to ensure the flow controller is more consistent state before calling external - // listener methods. - } - - /** - * Determine if the stream associated with {@code state} is writable. - * - * @param state The state which is associated with the stream to test writability for. - * @return {@code true} if {@link FlowState#stream()} is writable. {@code false} otherwise. - */ - final boolean isWritable(FlowState state) { - return isWritableConnection() && state.isWritable(); - } - - final void writePendingBytes() throws Http2Exception { - // Reentry is not permitted during the byte distribution process. It may lead to undesirable - // distribution of - // bytes and even infinite loops. We protect against reentry and make sure each call has an - // opportunity to - // cause a distribution to occur. This may be useful for example if the channel's writability - // changes from - // Writable -> Not Writable (because we are writing) -> Writable (because the user flushed to - // make more room - // in the channel outbound buffer). - if (inWritePendingBytes) { - return; - } - inWritePendingBytes = true; - try { - int bytesToWrite = writableBytes(); - // Make sure we always write at least once, regardless if we have bytesToWrite or not. - // This ensures that zero-length frames will always be written. - for (; ; ) { - Histogram.Timer distributedTimer = byteDistributedDuration.startTimer(); - boolean distributed = streamByteDistributor.distribute(bytesToWrite, this); - distributedTimer.observeDuration(); - if (!distributed || (bytesToWrite = writableBytes()) <= 0 || !isChannelWritable0()) { - break; - } - } - } finally { - inWritePendingBytes = false; - } - } - - void initialWindowSize(int newWindowSize) throws Http2Exception { - checkPositiveOrZero(newWindowSize, "newWindowSize"); - - final int delta = newWindowSize - initialWindowSize; - initialWindowSize = newWindowSize; - connection.forEachActiveStream( - new Http2StreamVisitor() { - @Override - public boolean visit(Http2Stream stream) throws Http2Exception { - state(stream).incrementStreamWindow(delta); - return true; - } - }); - - if (delta > 0 && isChannelWritable()) { - // The window size increased, send any pending frames for all streams. - writePendingBytes(); - } - } - - final boolean isWritableConnection() { - return connectionState.windowSize() - totalPendingBytes > 0 && isChannelWritable(); - } - } - - /** - * Writability of a {@code stream} is calculated using the following: - * - *

-   * Connection Window - Total Queued Bytes > 0 &&
-   * Stream Window - Bytes Queued for Stream > 0 &&
-   * isChannelWritable()
-   * 
- */ - private final class ListenerWritabilityMonitor extends WritabilityMonitor - implements Http2StreamVisitor { - private final Listener listener; - - ListenerWritabilityMonitor(Listener listener) { - this.listener = listener; - } - - @Override - public boolean visit(Http2Stream stream) throws Http2Exception { - FlowState state = state(stream); - if (isWritable(state) != state.markedWritability()) { - notifyWritabilityChanged(state); - } - return true; - } - - @Override - void windowSize(FlowState state, int initialWindowSize) { - super.windowSize(state, initialWindowSize); - try { - checkStateWritability(state); - } catch (Http2Exception e) { - throw new RuntimeException("Caught unexpected exception from window", e); - } - } - - @Override - void incrementWindowSize(FlowState state, int delta) throws Http2Exception { - super.incrementWindowSize(state, delta); - checkStateWritability(state); - } - - @Override - void initialWindowSize(int newWindowSize) throws Http2Exception { - super.initialWindowSize(newWindowSize); - if (isWritableConnection()) { - // If the write operation does not occur we still need to check all streams because they - // may have transitioned from writable to not writable. - checkAllWritabilityChanged(); - } - } - - @Override - void enqueueFrame(FlowState state, FlowControlled frame) throws Http2Exception { - super.enqueueFrame(state, frame); - checkConnectionThenStreamWritabilityChanged(state); - } - - @Override - void stateCancelled(FlowState state) { - try { - checkConnectionThenStreamWritabilityChanged(state); - } catch (Http2Exception e) { - throw new RuntimeException( - "Caught unexpected exception from checkAllWritabilityChanged", e); - } - } - - @Override - void channelWritabilityChange() throws Http2Exception { - if (connectionState.markedWritability() != isChannelWritable()) { - checkAllWritabilityChanged(); - } - } - - private void checkStateWritability(FlowState state) throws Http2Exception { - if (isWritable(state) != state.markedWritability()) { - if (state == connectionState) { - checkAllWritabilityChanged(); - } else { - notifyWritabilityChanged(state); - } - } - } - - private void notifyWritabilityChanged(FlowState state) { - state.markedWritability(!state.markedWritability()); - try { - listener.writabilityChanged(state.stream); - } catch (Throwable cause) { - logger.error("Caught Throwable from listener.writabilityChanged", cause); - } - } - - private void checkConnectionThenStreamWritabilityChanged(FlowState state) - throws Http2Exception { - // It is possible that the connection window and/or the individual stream writability could - // change. - if (isWritableConnection() != connectionState.markedWritability()) { - checkAllWritabilityChanged(); - } else if (isWritable(state) != state.markedWritability()) { - notifyWritabilityChanged(state); - } - } - - private void checkAllWritabilityChanged() throws Http2Exception { - // Make sure we mark that we have notified as a result of this change. - connectionState.markedWritability(isWritableConnection()); - connection.forEachActiveStream(this); - } - } -} diff --git a/src/main/java/io/netty/handler/codec/http2/Http2ConnectionHandler.java b/src/main/java/io/netty/handler/codec/http2/Http2ConnectionHandler.java deleted file mode 100644 index feb0f9dbbb7..00000000000 --- a/src/main/java/io/netty/handler/codec/http2/Http2ConnectionHandler.java +++ /dev/null @@ -1,1100 +0,0 @@ -/* - * Copyright 2014 The Netty Project - * - * The Netty Project licenses this file to you under the Apache License, version 2.0 (the - * "License"); you may not use this file except in compliance with the License. You may obtain a - * copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, 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. - */ -package io.netty.handler.codec.http2; - -import static io.netty.buffer.ByteBufUtil.hexDump; -import static io.netty.buffer.Unpooled.EMPTY_BUFFER; -import static io.netty.handler.codec.http2.Http2CodecUtil.HTTP_UPGRADE_STREAM_ID; -import static io.netty.handler.codec.http2.Http2CodecUtil.connectionPrefaceBuf; -import static io.netty.handler.codec.http2.Http2CodecUtil.getEmbeddedHttp2Exception; -import static io.netty.handler.codec.http2.Http2Error.INTERNAL_ERROR; -import static io.netty.handler.codec.http2.Http2Error.NO_ERROR; -import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR; -import static io.netty.handler.codec.http2.Http2Exception.connectionError; -import static io.netty.handler.codec.http2.Http2Exception.isStreamError; -import static io.netty.handler.codec.http2.Http2FrameTypes.SETTINGS; -import static io.netty.handler.codec.http2.Http2Stream.State.IDLE; -import static io.netty.util.CharsetUtil.UTF_8; -import static io.netty.util.internal.ObjectUtil.checkNotNull; -import static java.lang.Math.min; -import static java.util.concurrent.TimeUnit.MILLISECONDS; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufUtil; -import io.netty.buffer.Unpooled; -import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelFutureListener; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelOutboundHandler; -import io.netty.channel.ChannelPromise; -import io.netty.handler.codec.ByteToMessageDecoder; -import io.netty.handler.codec.http.HttpResponseStatus; -import io.netty.handler.codec.http2.Http2Exception.CompositeStreamException; -import io.netty.handler.codec.http2.Http2Exception.StreamException; -import io.netty.util.CharsetUtil; -import io.netty.util.concurrent.ScheduledFuture; -import io.netty.util.internal.UnstableApi; -import io.netty.util.internal.logging.InternalLogger; -import io.netty.util.internal.logging.InternalLoggerFactory; -import io.prometheus.client.Histogram; -import java.net.SocketAddress; -import java.util.List; -import java.util.concurrent.TimeUnit; -import org.tikv.common.util.HistogramUtils; - -/** - * Provides the default implementation for processing inbound frame events and delegates to a {@link - * Http2FrameListener} - * - *

This class will read HTTP/2 frames and delegate the events to a {@link Http2FrameListener} - * - *

This interface enforces inbound flow control functionality through {@link - * Http2LocalFlowController} - */ -@UnstableApi -public class Http2ConnectionHandler extends ByteToMessageDecoder - implements Http2LifecycleManager, ChannelOutboundHandler { - - private static final InternalLogger logger = - InternalLoggerFactory.getInstance(Http2ConnectionHandler.class); - - private static final Http2Headers HEADERS_TOO_LARGE_HEADERS = - ReadOnlyHttp2Headers.serverHeaders( - false, HttpResponseStatus.REQUEST_HEADER_FIELDS_TOO_LARGE.codeAsText()); - private static final ByteBuf HTTP_1_X_BUF = - Unpooled.unreleasableBuffer( - Unpooled.wrappedBuffer(new byte[] {'H', 'T', 'T', 'P', '/', '1', '.'})) - .asReadOnly(); - - private final Http2ConnectionDecoder decoder; - private final Http2ConnectionEncoder encoder; - private final Http2Settings initialSettings; - private final boolean decoupleCloseAndGoAway; - private ChannelFutureListener closeListener; - private BaseDecoder byteDecoder; - private long gracefulShutdownTimeoutMillis; - - public static final Histogram flushFlowControlWriteDuration = - HistogramUtils.buildDuration() - .name("netty_http2_flush_flow_control_write_duration_seconds") - .help("The time it takes to flush the pending bytes via flow control in seconds.") - .register(); - - public static final Histogram flushCtxFlushDuration = - HistogramUtils.buildDuration() - .name("netty_http2_flush_ctx_flush_duration_seconds") - .help("The time it takes to ctx flush in seconds.") - .register(); - - protected Http2ConnectionHandler( - Http2ConnectionDecoder decoder, - Http2ConnectionEncoder encoder, - Http2Settings initialSettings) { - this(decoder, encoder, initialSettings, false); - } - - protected Http2ConnectionHandler( - Http2ConnectionDecoder decoder, - Http2ConnectionEncoder encoder, - Http2Settings initialSettings, - boolean decoupleCloseAndGoAway) { - this.initialSettings = checkNotNull(initialSettings, "initialSettings"); - this.decoder = checkNotNull(decoder, "decoder"); - this.encoder = checkNotNull(encoder, "encoder"); - this.decoupleCloseAndGoAway = decoupleCloseAndGoAway; - if (encoder.connection() != decoder.connection()) { - throw new IllegalArgumentException( - "Encoder and Decoder do not share the same connection object"); - } - } - - /** - * Get the amount of time (in milliseconds) this endpoint will wait for all streams to be closed - * before closing the connection during the graceful shutdown process. Returns -1 if this - * connection is configured to wait indefinitely for all streams to close. - */ - public long gracefulShutdownTimeoutMillis() { - return gracefulShutdownTimeoutMillis; - } - - /** - * Set the amount of time (in milliseconds) this endpoint will wait for all streams to be closed - * before closing the connection during the graceful shutdown process. - * - * @param gracefulShutdownTimeoutMillis the amount of time (in milliseconds) this endpoint will - * wait for all streams to be closed before closing the connection during the graceful - * shutdown process. - */ - public void gracefulShutdownTimeoutMillis(long gracefulShutdownTimeoutMillis) { - if (gracefulShutdownTimeoutMillis < -1) { - throw new IllegalArgumentException( - "gracefulShutdownTimeoutMillis: " - + gracefulShutdownTimeoutMillis - + " (expected: -1 for indefinite or >= 0)"); - } - this.gracefulShutdownTimeoutMillis = gracefulShutdownTimeoutMillis; - } - - public Http2Connection connection() { - return encoder.connection(); - } - - public Http2ConnectionDecoder decoder() { - return decoder; - } - - public Http2ConnectionEncoder encoder() { - return encoder; - } - - private boolean prefaceSent() { - return byteDecoder != null && byteDecoder.prefaceSent(); - } - - /** - * Handles the client-side (cleartext) upgrade from HTTP to HTTP/2. Reserves local stream 1 for - * the HTTP/2 response. - */ - public void onHttpClientUpgrade() throws Http2Exception { - if (connection().isServer()) { - throw connectionError(PROTOCOL_ERROR, "Client-side HTTP upgrade requested for a server"); - } - if (!prefaceSent()) { - // If the preface was not sent yet it most likely means the handler was not added to the - // pipeline before - // calling this method. - throw connectionError(INTERNAL_ERROR, "HTTP upgrade must occur after preface was sent"); - } - if (decoder.prefaceReceived()) { - throw connectionError( - PROTOCOL_ERROR, "HTTP upgrade must occur before HTTP/2 preface is received"); - } - - // Create a local stream used for the HTTP cleartext upgrade. - connection().local().createStream(HTTP_UPGRADE_STREAM_ID, true); - } - - /** - * Handles the server-side (cleartext) upgrade from HTTP to HTTP/2. - * - * @param settings the settings for the remote endpoint. - */ - public void onHttpServerUpgrade(Http2Settings settings) throws Http2Exception { - if (!connection().isServer()) { - throw connectionError(PROTOCOL_ERROR, "Server-side HTTP upgrade requested for a client"); - } - if (!prefaceSent()) { - // If the preface was not sent yet it most likely means the handler was not added to the - // pipeline before - // calling this method. - throw connectionError(INTERNAL_ERROR, "HTTP upgrade must occur after preface was sent"); - } - if (decoder.prefaceReceived()) { - throw connectionError( - PROTOCOL_ERROR, "HTTP upgrade must occur before HTTP/2 preface is received"); - } - - // Apply the settings but no ACK is necessary. - encoder.remoteSettings(settings); - - // Create a stream in the half-closed state. - connection().remote().createStream(HTTP_UPGRADE_STREAM_ID, true); - } - - @Override - public void flush(ChannelHandlerContext ctx) { - try { - // Trigger pending writes in the remote flow controller. - Histogram.Timer writeTimer = flushFlowControlWriteDuration.startTimer(); - encoder.flowController().writePendingBytes(); - writeTimer.observeDuration(); - - Histogram.Timer flushTimer = flushCtxFlushDuration.startTimer(); - ctx.flush(); - flushTimer.observeDuration(); - } catch (Http2Exception e) { - onError(ctx, true, e); - } catch (Throwable cause) { - onError(ctx, true, connectionError(INTERNAL_ERROR, cause, "Error flushing")); - } - } - - private abstract class BaseDecoder { - public abstract void decode(ChannelHandlerContext ctx, ByteBuf in, List out) - throws Exception; - - public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {} - - public void channelActive(ChannelHandlerContext ctx) throws Exception {} - - public void channelInactive(ChannelHandlerContext ctx) throws Exception { - // Connection has terminated, close the encoder and decoder. - encoder().close(); - decoder().close(); - - // We need to remove all streams (not just the active ones). - // See https://github.com/netty/netty/issues/4838. - connection().close(ctx.voidPromise()); - } - - /** Determine if the HTTP/2 connection preface been sent. */ - public boolean prefaceSent() { - return true; - } - } - - private final class PrefaceDecoder extends BaseDecoder { - private ByteBuf clientPrefaceString; - private boolean prefaceSent; - - PrefaceDecoder(ChannelHandlerContext ctx) throws Exception { - clientPrefaceString = clientPrefaceString(encoder.connection()); - // This handler was just added to the context. In case it was handled after - // the connection became active, send the connection preface now. - sendPreface(ctx); - } - - @Override - public boolean prefaceSent() { - return prefaceSent; - } - - @Override - public void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { - try { - if (ctx.channel().isActive() - && readClientPrefaceString(in) - && verifyFirstFrameIsSettings(in)) { - // After the preface is read, it is time to hand over control to the post initialized - // decoder. - byteDecoder = new FrameDecoder(); - byteDecoder.decode(ctx, in, out); - } - } catch (Throwable e) { - onError(ctx, false, e); - } - } - - @Override - public void channelActive(ChannelHandlerContext ctx) throws Exception { - // The channel just became active - send the connection preface to the remote endpoint. - sendPreface(ctx); - } - - @Override - public void channelInactive(ChannelHandlerContext ctx) throws Exception { - cleanup(); - super.channelInactive(ctx); - } - - /** Releases the {@code clientPrefaceString}. Any active streams will be left in the open. */ - @Override - public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { - cleanup(); - } - - /** Releases the {@code clientPrefaceString}. Any active streams will be left in the open. */ - private void cleanup() { - if (clientPrefaceString != null) { - clientPrefaceString.release(); - clientPrefaceString = null; - } - } - - /** - * Decodes the client connection preface string from the input buffer. - * - * @return {@code true} if processing of the client preface string is complete. Since client - * preface strings can only be received by servers, returns true immediately for client - * endpoints. - */ - private boolean readClientPrefaceString(ByteBuf in) throws Http2Exception { - if (clientPrefaceString == null) { - return true; - } - - int prefaceRemaining = clientPrefaceString.readableBytes(); - int bytesRead = min(in.readableBytes(), prefaceRemaining); - - // If the input so far doesn't match the preface, break the connection. - if (bytesRead == 0 - || !ByteBufUtil.equals( - in, - in.readerIndex(), - clientPrefaceString, - clientPrefaceString.readerIndex(), - bytesRead)) { - int maxSearch = 1024; // picked because 512 is too little, and 2048 too much - int http1Index = - ByteBufUtil.indexOf( - HTTP_1_X_BUF, in.slice(in.readerIndex(), min(in.readableBytes(), maxSearch))); - if (http1Index != -1) { - String chunk = - in.toString(in.readerIndex(), http1Index - in.readerIndex(), CharsetUtil.US_ASCII); - throw connectionError(PROTOCOL_ERROR, "Unexpected HTTP/1.x request: %s", chunk); - } - String receivedBytes = - hexDump( - in, in.readerIndex(), min(in.readableBytes(), clientPrefaceString.readableBytes())); - throw connectionError( - PROTOCOL_ERROR, - "HTTP/2 client preface string missing or corrupt. " + "Hex dump for received bytes: %s", - receivedBytes); - } - in.skipBytes(bytesRead); - clientPrefaceString.skipBytes(bytesRead); - - if (!clientPrefaceString.isReadable()) { - // Entire preface has been read. - clientPrefaceString.release(); - clientPrefaceString = null; - return true; - } - return false; - } - - /** - * Peeks at that the next frame in the buffer and verifies that it is a non-ack {@code SETTINGS} - * frame. - * - * @param in the inbound buffer. - * @return {@code true} if the next frame is a non-ack {@code SETTINGS} frame, {@code false} if - * more data is required before we can determine the next frame type. - * @throws Http2Exception thrown if the next frame is NOT a non-ack {@code SETTINGS} frame. - */ - private boolean verifyFirstFrameIsSettings(ByteBuf in) throws Http2Exception { - if (in.readableBytes() < 5) { - // Need more data before we can see the frame type for the first frame. - return false; - } - - short frameType = in.getUnsignedByte(in.readerIndex() + 3); - short flags = in.getUnsignedByte(in.readerIndex() + 4); - if (frameType != SETTINGS || (flags & Http2Flags.ACK) != 0) { - throw connectionError( - PROTOCOL_ERROR, - "First received frame was not SETTINGS. " + "Hex dump for first 5 bytes: %s", - hexDump(in, in.readerIndex(), 5)); - } - return true; - } - - /** - * Sends the HTTP/2 connection preface upon establishment of the connection, if not already - * sent. - */ - private void sendPreface(ChannelHandlerContext ctx) throws Exception { - if (prefaceSent || !ctx.channel().isActive()) { - return; - } - - prefaceSent = true; - - final boolean isClient = !connection().isServer(); - if (isClient) { - // Clients must send the preface string as the first bytes on the connection. - ctx.write(connectionPrefaceBuf()).addListener(ChannelFutureListener.CLOSE_ON_FAILURE); - } - - // Both client and server must send their initial settings. - encoder - .writeSettings(ctx, initialSettings, ctx.newPromise()) - .addListener(ChannelFutureListener.CLOSE_ON_FAILURE); - - if (isClient) { - // If this handler is extended by the user and we directly fire the userEvent from this - // context then - // the user will not see the event. We should fire the event starting with this handler so - // this class - // (and extending classes) have a chance to process the event. - userEventTriggered(ctx, Http2ConnectionPrefaceAndSettingsFrameWrittenEvent.INSTANCE); - } - } - } - - private final class FrameDecoder extends BaseDecoder { - @Override - public void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { - try { - decoder.decodeFrame(ctx, in, out); - } catch (Throwable e) { - onError(ctx, false, e); - } - } - } - - @Override - public void handlerAdded(ChannelHandlerContext ctx) throws Exception { - // Initialize the encoder, decoder, flow controllers, and internal state. - encoder.lifecycleManager(this); - decoder.lifecycleManager(this); - encoder.flowController().channelHandlerContext(ctx); - decoder.flowController().channelHandlerContext(ctx); - byteDecoder = new PrefaceDecoder(ctx); - } - - @Override - protected void handlerRemoved0(ChannelHandlerContext ctx) throws Exception { - if (byteDecoder != null) { - byteDecoder.handlerRemoved(ctx); - byteDecoder = null; - } - } - - @Override - public void channelActive(ChannelHandlerContext ctx) throws Exception { - if (byteDecoder == null) { - byteDecoder = new PrefaceDecoder(ctx); - } - byteDecoder.channelActive(ctx); - super.channelActive(ctx); - } - - @Override - public void channelInactive(ChannelHandlerContext ctx) throws Exception { - // Call super class first, as this may result in decode being called. - super.channelInactive(ctx); - if (byteDecoder != null) { - byteDecoder.channelInactive(ctx); - byteDecoder = null; - } - } - - @Override - public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception { - // Writability is expected to change while we are writing. We cannot allow this event to trigger - // reentering - // the allocation and write loop. Reentering the event loop will lead to over or illegal - // allocation. - try { - if (ctx.channel().isWritable()) { - flush(ctx); - } - encoder.flowController().channelWritabilityChanged(); - } finally { - super.channelWritabilityChanged(ctx); - } - } - - @Override - protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { - byteDecoder.decode(ctx, in, out); - } - - @Override - public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) - throws Exception { - ctx.bind(localAddress, promise); - } - - @Override - public void connect( - ChannelHandlerContext ctx, - SocketAddress remoteAddress, - SocketAddress localAddress, - ChannelPromise promise) - throws Exception { - ctx.connect(remoteAddress, localAddress, promise); - } - - @Override - public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { - ctx.disconnect(promise); - } - - @Override - public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { - if (decoupleCloseAndGoAway) { - ctx.close(promise); - return; - } - promise = promise.unvoid(); - // Avoid NotYetConnectedException - if (!ctx.channel().isActive()) { - ctx.close(promise); - return; - } - - // If the user has already sent a GO_AWAY frame they may be attempting to do a graceful shutdown - // which requires - // sending multiple GO_AWAY frames. We should only send a GO_AWAY here if one has not already - // been sent. If - // a GO_AWAY has been sent we send a empty buffer just so we can wait to close until all other - // data has been - // flushed to the OS. - // https://github.com/netty/netty/issues/5307 - ChannelFuture f = - connection().goAwaySent() ? ctx.write(EMPTY_BUFFER) : goAway(ctx, null, ctx.newPromise()); - ctx.flush(); - doGracefulShutdown(ctx, f, promise); - } - - private ChannelFutureListener newClosingChannelFutureListener( - ChannelHandlerContext ctx, ChannelPromise promise) { - long gracefulShutdownTimeoutMillis = this.gracefulShutdownTimeoutMillis; - return gracefulShutdownTimeoutMillis < 0 - ? new ClosingChannelFutureListener(ctx, promise) - : new ClosingChannelFutureListener( - ctx, promise, gracefulShutdownTimeoutMillis, MILLISECONDS); - } - - private void doGracefulShutdown( - ChannelHandlerContext ctx, ChannelFuture future, final ChannelPromise promise) { - final ChannelFutureListener listener = newClosingChannelFutureListener(ctx, promise); - if (isGracefulShutdownComplete()) { - // If there are no active streams, close immediately after the GO_AWAY write completes or the - // timeout - // elapsed. - future.addListener(listener); - } else { - // If there are active streams we should wait until they are all closed before closing the - // connection. - - // The ClosingChannelFutureListener will cascade promise completion. We need to always notify - // the - // new ClosingChannelFutureListener when the graceful close completes if the promise is not - // null. - if (closeListener == null) { - closeListener = listener; - } else if (promise != null) { - final ChannelFutureListener oldCloseListener = closeListener; - closeListener = - new ChannelFutureListener() { - @Override - public void operationComplete(ChannelFuture future) throws Exception { - try { - oldCloseListener.operationComplete(future); - } finally { - listener.operationComplete(future); - } - } - }; - } - } - } - - @Override - public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { - ctx.deregister(promise); - } - - @Override - public void read(ChannelHandlerContext ctx) throws Exception { - ctx.read(); - } - - @Override - public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) - throws Exception { - ctx.write(msg, promise); - } - - @Override - public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { - // Trigger flush after read on the assumption that flush is cheap if there is nothing to write - // and that - // for flow-control the read may release window that causes data to be written that can now be - // flushed. - try { - // First call channelReadComplete0(...) as this may produce more data that we want to flush - channelReadComplete0(ctx); - } finally { - flush(ctx); - } - } - - final void channelReadComplete0(ChannelHandlerContext ctx) { - // Discard bytes of the cumulation buffer if needed. - discardSomeReadBytes(); - - // Ensure we never stale the HTTP/2 Channel. Flow-control is enforced by HTTP/2. - // - // See https://tools.ietf.org/html/rfc7540#section-5.2.2 - if (!ctx.channel().config().isAutoRead()) { - ctx.read(); - } - - ctx.fireChannelReadComplete(); - } - - /** - * Handles {@link Http2Exception} objects that were thrown from other handlers. Ignores all other - * exceptions. - */ - @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { - if (getEmbeddedHttp2Exception(cause) != null) { - // Some exception in the causality chain is an Http2Exception - handle it. - onError(ctx, false, cause); - } else { - super.exceptionCaught(ctx, cause); - } - } - - /** - * Closes the local side of the given stream. If this causes the stream to be closed, adds a hook - * to close the channel after the given future completes. - * - * @param stream the stream to be half closed. - * @param future If closing, the future after which to close the channel. - */ - @Override - public void closeStreamLocal(Http2Stream stream, ChannelFuture future) { - switch (stream.state()) { - case HALF_CLOSED_LOCAL: - case OPEN: - stream.closeLocalSide(); - break; - default: - closeStream(stream, future); - break; - } - } - - /** - * Closes the remote side of the given stream. If this causes the stream to be closed, adds a hook - * to close the channel after the given future completes. - * - * @param stream the stream to be half closed. - * @param future If closing, the future after which to close the channel. - */ - @Override - public void closeStreamRemote(Http2Stream stream, ChannelFuture future) { - switch (stream.state()) { - case HALF_CLOSED_REMOTE: - case OPEN: - stream.closeRemoteSide(); - break; - default: - closeStream(stream, future); - break; - } - } - - @Override - public void closeStream(final Http2Stream stream, ChannelFuture future) { - stream.close(); - - if (future.isDone()) { - checkCloseConnection(future); - } else { - future.addListener( - new ChannelFutureListener() { - @Override - public void operationComplete(ChannelFuture future) throws Exception { - checkCloseConnection(future); - } - }); - } - } - - /** Central handler for all exceptions caught during HTTP/2 processing. */ - @Override - public void onError(ChannelHandlerContext ctx, boolean outbound, Throwable cause) { - Http2Exception embedded = getEmbeddedHttp2Exception(cause); - if (isStreamError(embedded)) { - onStreamError(ctx, outbound, cause, (StreamException) embedded); - } else if (embedded instanceof CompositeStreamException) { - CompositeStreamException compositException = (CompositeStreamException) embedded; - for (StreamException streamException : compositException) { - onStreamError(ctx, outbound, cause, streamException); - } - } else { - onConnectionError(ctx, outbound, cause, embedded); - } - ctx.flush(); - } - - /** - * Called by the graceful shutdown logic to determine when it is safe to close the connection. - * Returns {@code true} if the graceful shutdown has completed and the connection can be safely - * closed. This implementation just guarantees that there are no active streams. Subclasses may - * override to provide additional checks. - */ - protected boolean isGracefulShutdownComplete() { - return connection().numActiveStreams() == 0; - } - - /** - * Handler for a connection error. Sends a GO_AWAY frame to the remote endpoint. Once all streams - * are closed, the connection is shut down. - * - * @param ctx the channel context - * @param outbound {@code true} if the error was caused by an outbound operation. - * @param cause the exception that was caught - * @param http2Ex the {@link Http2Exception} that is embedded in the causality chain. This may be - * {@code null} if it's an unknown exception. - */ - protected void onConnectionError( - ChannelHandlerContext ctx, boolean outbound, Throwable cause, Http2Exception http2Ex) { - if (http2Ex == null) { - http2Ex = new Http2Exception(INTERNAL_ERROR, cause.getMessage(), cause); - } - - ChannelPromise promise = ctx.newPromise(); - ChannelFuture future = goAway(ctx, http2Ex, ctx.newPromise()); - if (http2Ex.shutdownHint() == Http2Exception.ShutdownHint.GRACEFUL_SHUTDOWN) { - doGracefulShutdown(ctx, future, promise); - } else { - future.addListener(newClosingChannelFutureListener(ctx, promise)); - } - } - - /** - * Handler for a stream error. Sends a {@code RST_STREAM} frame to the remote endpoint and closes - * the stream. - * - * @param ctx the channel context - * @param outbound {@code true} if the error was caused by an outbound operation. - * @param cause the exception that was caught - * @param http2Ex the {@link StreamException} that is embedded in the causality chain. - */ - protected void onStreamError( - ChannelHandlerContext ctx, - boolean outbound, - @SuppressWarnings("unused") Throwable cause, - StreamException http2Ex) { - final int streamId = http2Ex.streamId(); - Http2Stream stream = connection().stream(streamId); - - // if this is caused by reading headers that are too large, send a header with status 431 - if (http2Ex instanceof Http2Exception.HeaderListSizeException - && ((Http2Exception.HeaderListSizeException) http2Ex).duringDecode() - && connection().isServer()) { - - // NOTE We have to check to make sure that a stream exists before we send our reply. - // We likely always create the stream below as the stream isn't created until the - // header block is completely processed. - - // The case of a streamId referring to a stream which was already closed is handled - // by createStream and will land us in the catch block below - if (stream == null) { - try { - stream = encoder.connection().remote().createStream(streamId, true); - } catch (Http2Exception e) { - resetUnknownStream(ctx, streamId, http2Ex.error().code(), ctx.newPromise()); - return; - } - } - - // ensure that we have not already sent headers on this stream - if (stream != null && !stream.isHeadersSent()) { - try { - handleServerHeaderDecodeSizeError(ctx, stream); - } catch (Throwable cause2) { - onError(ctx, outbound, connectionError(INTERNAL_ERROR, cause2, "Error DecodeSizeError")); - } - } - } - - if (stream == null) { - if (!outbound || connection().local().mayHaveCreatedStream(streamId)) { - resetUnknownStream(ctx, streamId, http2Ex.error().code(), ctx.newPromise()); - } - } else { - resetStream(ctx, stream, http2Ex.error().code(), ctx.newPromise()); - } - } - - /** - * Notifies client that this server has received headers that are larger than what it is willing - * to accept. Override to change behavior. - * - * @param ctx the channel context - * @param stream the Http2Stream on which the header was received - */ - protected void handleServerHeaderDecodeSizeError(ChannelHandlerContext ctx, Http2Stream stream) { - encoder().writeHeaders(ctx, stream.id(), HEADERS_TOO_LARGE_HEADERS, 0, true, ctx.newPromise()); - } - - protected Http2FrameWriter frameWriter() { - return encoder().frameWriter(); - } - - /** - * Sends a {@code RST_STREAM} frame even if we don't know about the stream. This error condition - * is most likely triggered by the first frame of a stream being invalid. That is, there was an - * error reading the frame before we could create a new stream. - */ - private ChannelFuture resetUnknownStream( - final ChannelHandlerContext ctx, int streamId, long errorCode, ChannelPromise promise) { - ChannelFuture future = frameWriter().writeRstStream(ctx, streamId, errorCode, promise); - if (future.isDone()) { - closeConnectionOnError(ctx, future); - } else { - future.addListener( - new ChannelFutureListener() { - @Override - public void operationComplete(ChannelFuture future) throws Exception { - closeConnectionOnError(ctx, future); - } - }); - } - return future; - } - - @Override - public ChannelFuture resetStream( - final ChannelHandlerContext ctx, int streamId, long errorCode, ChannelPromise promise) { - final Http2Stream stream = connection().stream(streamId); - if (stream == null) { - return resetUnknownStream(ctx, streamId, errorCode, promise.unvoid()); - } - - return resetStream(ctx, stream, errorCode, promise); - } - - private ChannelFuture resetStream( - final ChannelHandlerContext ctx, - final Http2Stream stream, - long errorCode, - ChannelPromise promise) { - promise = promise.unvoid(); - if (stream.isResetSent()) { - // Don't write a RST_STREAM frame if we have already written one. - return promise.setSuccess(); - } - // Synchronously set the resetSent flag to prevent any subsequent calls - // from resulting in multiple reset frames being sent. - // - // This needs to be done before we notify the promise as the promise may have a listener - // attached that - // call resetStream(...) again. - stream.resetSent(); - - final ChannelFuture future; - // If the remote peer is not aware of the steam, then we are not allowed to send a RST_STREAM - // https://tools.ietf.org/html/rfc7540#section-6.4. - if (stream.state() == IDLE - || connection().local().created(stream) - && !stream.isHeadersSent() - && !stream.isPushPromiseSent()) { - future = promise.setSuccess(); - } else { - future = frameWriter().writeRstStream(ctx, stream.id(), errorCode, promise); - } - if (future.isDone()) { - processRstStreamWriteResult(ctx, stream, future); - } else { - future.addListener( - new ChannelFutureListener() { - @Override - public void operationComplete(ChannelFuture future) throws Exception { - processRstStreamWriteResult(ctx, stream, future); - } - }); - } - - return future; - } - - @Override - public ChannelFuture goAway( - final ChannelHandlerContext ctx, - final int lastStreamId, - final long errorCode, - final ByteBuf debugData, - ChannelPromise promise) { - promise = promise.unvoid(); - final Http2Connection connection = connection(); - try { - if (!connection.goAwaySent(lastStreamId, errorCode, debugData)) { - debugData.release(); - promise.trySuccess(); - return promise; - } - } catch (Throwable cause) { - debugData.release(); - promise.tryFailure(cause); - return promise; - } - - // Need to retain before we write the buffer because if we do it after the refCnt could already - // be 0 and - // result in an IllegalRefCountException. - debugData.retain(); - ChannelFuture future = - frameWriter().writeGoAway(ctx, lastStreamId, errorCode, debugData, promise); - - if (future.isDone()) { - processGoAwayWriteResult(ctx, lastStreamId, errorCode, debugData, future); - } else { - future.addListener( - new ChannelFutureListener() { - @Override - public void operationComplete(ChannelFuture future) throws Exception { - processGoAwayWriteResult(ctx, lastStreamId, errorCode, debugData, future); - } - }); - } - - return future; - } - - /** - * Closes the connection if the graceful shutdown process has completed. - * - * @param future Represents the status that will be passed to the {@link #closeListener}. - */ - private void checkCloseConnection(ChannelFuture future) { - // If this connection is closing and the graceful shutdown has completed, close the connection - // once this operation completes. - if (closeListener != null && isGracefulShutdownComplete()) { - ChannelFutureListener closeListener = this.closeListener; - // This method could be called multiple times - // and we don't want to notify the closeListener multiple times. - this.closeListener = null; - try { - closeListener.operationComplete(future); - } catch (Exception e) { - throw new IllegalStateException("Close listener threw an unexpected exception", e); - } - } - } - - /** - * Close the remote endpoint with with a {@code GO_AWAY} frame. Does not flush - * immediately, this is the responsibility of the caller. - */ - private ChannelFuture goAway( - ChannelHandlerContext ctx, Http2Exception cause, ChannelPromise promise) { - long errorCode = cause != null ? cause.error().code() : NO_ERROR.code(); - int lastKnownStream = connection().remote().lastStreamCreated(); - return goAway(ctx, lastKnownStream, errorCode, Http2CodecUtil.toByteBuf(ctx, cause), promise); - } - - private void processRstStreamWriteResult( - ChannelHandlerContext ctx, Http2Stream stream, ChannelFuture future) { - if (future.isSuccess()) { - closeStream(stream, future); - } else { - // The connection will be closed and so no need to change the resetSent flag to false. - onConnectionError(ctx, true, future.cause(), null); - } - } - - private void closeConnectionOnError(ChannelHandlerContext ctx, ChannelFuture future) { - if (!future.isSuccess()) { - onConnectionError(ctx, true, future.cause(), null); - } - } - - /** - * Returns the client preface string if this is a client connection, otherwise returns {@code - * null}. - */ - private static ByteBuf clientPrefaceString(Http2Connection connection) { - return connection.isServer() ? connectionPrefaceBuf() : null; - } - - private static void processGoAwayWriteResult( - final ChannelHandlerContext ctx, - final int lastStreamId, - final long errorCode, - final ByteBuf debugData, - ChannelFuture future) { - try { - if (future.isSuccess()) { - if (errorCode != NO_ERROR.code()) { - if (logger.isDebugEnabled()) { - logger.debug( - "{} Sent GOAWAY: lastStreamId '{}', errorCode '{}', " - + "debugData '{}'. Forcing shutdown of the connection.", - ctx.channel(), - lastStreamId, - errorCode, - debugData.toString(UTF_8), - future.cause()); - } - ctx.close(); - } - } else { - if (logger.isDebugEnabled()) { - logger.debug( - "{} Sending GOAWAY failed: lastStreamId '{}', errorCode '{}', " - + "debugData '{}'. Forcing shutdown of the connection.", - ctx.channel(), - lastStreamId, - errorCode, - debugData.toString(UTF_8), - future.cause()); - } - ctx.close(); - } - } finally { - // We're done with the debug data now. - debugData.release(); - } - } - - /** Closes the channel when the future completes. */ - private static final class ClosingChannelFutureListener implements ChannelFutureListener { - private final ChannelHandlerContext ctx; - private final ChannelPromise promise; - private final ScheduledFuture timeoutTask; - private boolean closed; - - ClosingChannelFutureListener(ChannelHandlerContext ctx, ChannelPromise promise) { - this.ctx = ctx; - this.promise = promise; - timeoutTask = null; - } - - ClosingChannelFutureListener( - final ChannelHandlerContext ctx, - final ChannelPromise promise, - long timeout, - TimeUnit unit) { - this.ctx = ctx; - this.promise = promise; - timeoutTask = - ctx.executor() - .schedule( - new Runnable() { - @Override - public void run() { - doClose(); - } - }, - timeout, - unit); - } - - @Override - public void operationComplete(ChannelFuture sentGoAwayFuture) { - if (timeoutTask != null) { - timeoutTask.cancel(false); - } - doClose(); - } - - private void doClose() { - // We need to guard against multiple calls as the timeout may trigger close() first and then - // it will be - // triggered again because of operationComplete(...) is called. - if (closed) { - // This only happens if we also scheduled a timeout task. - assert timeoutTask != null; - return; - } - closed = true; - if (promise == null) { - ctx.close(); - } else { - ctx.close(promise); - } - } - } -} diff --git a/src/main/java/io/netty/handler/codec/http2/Http2OutboundFrameLogger.java b/src/main/java/io/netty/handler/codec/http2/Http2OutboundFrameLogger.java deleted file mode 100644 index 3279b27e5a3..00000000000 --- a/src/main/java/io/netty/handler/codec/http2/Http2OutboundFrameLogger.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright 2014 The Netty Project - * - * The Netty Project licenses this file to you under the Apache License, - * version 2.0 (the "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, 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. - */ -package io.netty.handler.codec.http2; - -import static io.netty.handler.codec.http2.Http2FrameLogger.Direction.OUTBOUND; -import static io.netty.util.internal.ObjectUtil.checkNotNull; - -import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelPromise; -import io.netty.util.internal.UnstableApi; -import io.prometheus.client.Histogram; -import org.tikv.common.util.HistogramUtils; - -/** - * Decorator around a {@link Http2FrameWriter} that logs all outbound frames before calling the - * writer. - */ -@UnstableApi -public class Http2OutboundFrameLogger implements Http2FrameWriter { - private final Http2FrameWriter writer; - private final Http2FrameLogger logger; - - public static final Histogram writeHeaderLogDuration = - HistogramUtils.buildDuration() - .name("netty_http2_write_header_log_duration_seconds") - .help("HTTP/2 write header log duration in seconds") - .register(); - - public Http2OutboundFrameLogger(Http2FrameWriter writer, Http2FrameLogger logger) { - this.writer = checkNotNull(writer, "writer"); - this.logger = checkNotNull(logger, "logger"); - } - - @Override - public ChannelFuture writeData( - ChannelHandlerContext ctx, - int streamId, - ByteBuf data, - int padding, - boolean endStream, - ChannelPromise promise) { - logger.logData(OUTBOUND, ctx, streamId, data, padding, endStream); - return writer.writeData(ctx, streamId, data, padding, endStream, promise); - } - - @Override - public ChannelFuture writeHeaders( - ChannelHandlerContext ctx, - int streamId, - Http2Headers headers, - int padding, - boolean endStream, - ChannelPromise promise) { - Histogram.Timer logTimer = writeHeaderLogDuration.startTimer(); - logger.logHeaders(OUTBOUND, ctx, streamId, headers, padding, endStream); - logTimer.observeDuration(); - return writer.writeHeaders(ctx, streamId, headers, padding, endStream, promise); - } - - @Override - public ChannelFuture writeHeaders( - ChannelHandlerContext ctx, - int streamId, - Http2Headers headers, - int streamDependency, - short weight, - boolean exclusive, - int padding, - boolean endStream, - ChannelPromise promise) { - Histogram.Timer logTimer = writeHeaderLogDuration.startTimer(); - logger.logHeaders( - OUTBOUND, ctx, streamId, headers, streamDependency, weight, exclusive, padding, endStream); - logTimer.observeDuration(); - return writer.writeHeaders( - ctx, streamId, headers, streamDependency, weight, exclusive, padding, endStream, promise); - } - - @Override - public ChannelFuture writePriority( - ChannelHandlerContext ctx, - int streamId, - int streamDependency, - short weight, - boolean exclusive, - ChannelPromise promise) { - logger.logPriority(OUTBOUND, ctx, streamId, streamDependency, weight, exclusive); - return writer.writePriority(ctx, streamId, streamDependency, weight, exclusive, promise); - } - - @Override - public ChannelFuture writeRstStream( - ChannelHandlerContext ctx, int streamId, long errorCode, ChannelPromise promise) { - logger.logRstStream(OUTBOUND, ctx, streamId, errorCode); - return writer.writeRstStream(ctx, streamId, errorCode, promise); - } - - @Override - public ChannelFuture writeSettings( - ChannelHandlerContext ctx, Http2Settings settings, ChannelPromise promise) { - logger.logSettings(OUTBOUND, ctx, settings); - return writer.writeSettings(ctx, settings, promise); - } - - @Override - public ChannelFuture writeSettingsAck(ChannelHandlerContext ctx, ChannelPromise promise) { - logger.logSettingsAck(OUTBOUND, ctx); - return writer.writeSettingsAck(ctx, promise); - } - - @Override - public ChannelFuture writePing( - ChannelHandlerContext ctx, boolean ack, long data, ChannelPromise promise) { - if (ack) { - logger.logPingAck(OUTBOUND, ctx, data); - } else { - logger.logPing(OUTBOUND, ctx, data); - } - return writer.writePing(ctx, ack, data, promise); - } - - @Override - public ChannelFuture writePushPromise( - ChannelHandlerContext ctx, - int streamId, - int promisedStreamId, - Http2Headers headers, - int padding, - ChannelPromise promise) { - logger.logPushPromise(OUTBOUND, ctx, streamId, promisedStreamId, headers, padding); - return writer.writePushPromise(ctx, streamId, promisedStreamId, headers, padding, promise); - } - - @Override - public ChannelFuture writeGoAway( - ChannelHandlerContext ctx, - int lastStreamId, - long errorCode, - ByteBuf debugData, - ChannelPromise promise) { - logger.logGoAway(OUTBOUND, ctx, lastStreamId, errorCode, debugData); - return writer.writeGoAway(ctx, lastStreamId, errorCode, debugData, promise); - } - - @Override - public ChannelFuture writeWindowUpdate( - ChannelHandlerContext ctx, int streamId, int windowSizeIncrement, ChannelPromise promise) { - logger.logWindowsUpdate(OUTBOUND, ctx, streamId, windowSizeIncrement); - return writer.writeWindowUpdate(ctx, streamId, windowSizeIncrement, promise); - } - - @Override - public ChannelFuture writeFrame( - ChannelHandlerContext ctx, - byte frameType, - int streamId, - Http2Flags flags, - ByteBuf payload, - ChannelPromise promise) { - logger.logUnknownFrame(OUTBOUND, ctx, frameType, streamId, flags, payload); - return writer.writeFrame(ctx, frameType, streamId, flags, payload, promise); - } - - @Override - public void close() { - writer.close(); - } - - @Override - public Configuration configuration() { - return writer.configuration(); - } -}