github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/cdc/sink/dmlsink/mq/worker_test.go (about)

     1  // Copyright 2022 PingCAP, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package mq
    15  
    16  import (
    17  	"context"
    18  	"sync"
    19  	"testing"
    20  	"time"
    21  
    22  	"github.com/pingcap/tidb/pkg/parser/mysql"
    23  	"github.com/pingcap/tiflow/cdc/entry"
    24  	"github.com/pingcap/tiflow/cdc/model"
    25  	"github.com/pingcap/tiflow/cdc/sink/dmlsink"
    26  	"github.com/pingcap/tiflow/cdc/sink/dmlsink/mq/dmlproducer"
    27  	"github.com/pingcap/tiflow/cdc/sink/metrics"
    28  	"github.com/pingcap/tiflow/cdc/sink/tablesink/state"
    29  	"github.com/pingcap/tiflow/pkg/config"
    30  	"github.com/pingcap/tiflow/pkg/sink"
    31  	"github.com/pingcap/tiflow/pkg/sink/codec"
    32  	"github.com/pingcap/tiflow/pkg/sink/codec/builder"
    33  	"github.com/pingcap/tiflow/pkg/sink/codec/common"
    34  	"github.com/stretchr/testify/require"
    35  )
    36  
    37  func newBatchEncodeWorker(ctx context.Context, t *testing.T) (*worker, dmlproducer.DMLProducer) {
    38  	id := model.DefaultChangeFeedID("test")
    39  	// 200 is about the size of a rowEvent change.
    40  	encoderConfig := common.NewConfig(config.ProtocolOpen).WithMaxMessageBytes(200).WithChangefeedID(id)
    41  	encoderBuilder, err := builder.NewRowEventEncoderBuilder(context.Background(), encoderConfig)
    42  	require.NoError(t, err)
    43  	p := dmlproducer.NewDMLMockProducer(context.Background(), id, nil, nil, nil, nil)
    44  	require.NoError(t, err)
    45  	encoderConcurrency := 4
    46  	statistics := metrics.NewStatistics(ctx, id, sink.RowSink)
    47  	cfg := config.GetDefaultReplicaConfig()
    48  	cfg.Sink.EncoderConcurrency = &encoderConcurrency
    49  	encoderGroup := codec.NewEncoderGroup(cfg.Sink, encoderBuilder, id)
    50  	return newWorker(id, config.ProtocolOpen, p, encoderGroup, statistics), p
    51  }
    52  
    53  func newNonBatchEncodeWorker(ctx context.Context, t *testing.T) (*worker, dmlproducer.DMLProducer) {
    54  	id := model.DefaultChangeFeedID("test")
    55  	// 300 is about the size of a rowEvent change.
    56  	encoderConfig := common.NewConfig(config.ProtocolCanalJSON).WithMaxMessageBytes(300).WithChangefeedID(id)
    57  	encoderBuilder, err := builder.NewRowEventEncoderBuilder(context.Background(), encoderConfig)
    58  	require.NoError(t, err)
    59  	p := dmlproducer.NewDMLMockProducer(context.Background(), id, nil, nil, nil, nil)
    60  	require.NoError(t, err)
    61  	encoderConcurrency := 4
    62  	statistics := metrics.NewStatistics(ctx, id, sink.RowSink)
    63  	cfg := config.GetDefaultReplicaConfig()
    64  	cfg.Sink.EncoderConcurrency = &encoderConcurrency
    65  	encoderGroup := codec.NewEncoderGroup(cfg.Sink, encoderBuilder, id)
    66  	return newWorker(id, config.ProtocolOpen, p, encoderGroup, statistics), p
    67  }
    68  
    69  func TestNonBatchEncode_SendMessages(t *testing.T) {
    70  	helper := entry.NewSchemaTestHelper(t)
    71  	defer helper.Close()
    72  
    73  	sql := `create table test.t(a varchar(255) primary key)`
    74  	job := helper.DDL2Job(sql)
    75  	tableInfo := model.WrapTableInfo(0, "test", 1, job.BinlogInfo.TableInfo)
    76  
    77  	ctx, cancel := context.WithCancel(context.Background())
    78  	defer cancel()
    79  
    80  	worker, p := newNonBatchEncodeWorker(ctx, t)
    81  	defer worker.close()
    82  
    83  	key := model.TopicPartitionKey{
    84  		Topic:     "test",
    85  		Partition: 1,
    86  	}
    87  	row := &model.RowChangedEvent{
    88  		CommitTs:  1,
    89  		TableInfo: tableInfo,
    90  		Columns:   model.Columns2ColumnDatas([]*model.Column{{Name: "a", Value: "aa"}}, tableInfo),
    91  	}
    92  	tableStatus := state.TableSinkSinking
    93  
    94  	count := 512
    95  	total := 0
    96  	expected := 0
    97  	for i := 0; i < count; i++ {
    98  		expected += i
    99  
   100  		bit := i
   101  		worker.msgChan.In() <- mqEvent{
   102  			key: key,
   103  			rowEvent: &dmlsink.RowChangeCallbackableEvent{
   104  				Event: row,
   105  				Callback: func() {
   106  					total += bit
   107  				},
   108  				SinkState: &tableStatus,
   109  			},
   110  		}
   111  	}
   112  
   113  	var wg sync.WaitGroup
   114  	wg.Add(1)
   115  	go func() {
   116  		defer wg.Done()
   117  		_ = worker.run(ctx)
   118  	}()
   119  
   120  	mp := p.(*dmlproducer.MockDMLProducer)
   121  	require.Eventually(t, func() bool {
   122  		return len(mp.GetAllEvents()) == count
   123  	}, 3*time.Second, 100*time.Millisecond)
   124  
   125  	require.Eventually(t, func() bool {
   126  		return total == expected
   127  	}, 3*time.Second, 10*time.Millisecond)
   128  	cancel()
   129  
   130  	wg.Wait()
   131  }
   132  
   133  func TestBatchEncode_Batch(t *testing.T) {
   134  	t.Parallel()
   135  
   136  	ctx, cancel := context.WithCancel(context.Background())
   137  	defer cancel()
   138  	worker, _ := newBatchEncodeWorker(ctx, t)
   139  	defer worker.close()
   140  	key := model.TopicPartitionKey{
   141  		Topic:     "test",
   142  		Partition: 1,
   143  	}
   144  	tableStatus := state.TableSinkSinking
   145  	cols := []*model.Column{{Name: "col1", Type: 1, Value: "aa"}}
   146  	tableInfo := model.BuildTableInfo("a", "b", cols, nil)
   147  	row := &model.RowChangedEvent{
   148  		CommitTs:  1,
   149  		TableInfo: tableInfo,
   150  		Columns:   model.Columns2ColumnDatas(cols, tableInfo),
   151  	}
   152  
   153  	for i := 0; i < 512; i++ {
   154  		worker.msgChan.In() <- mqEvent{
   155  			key: key,
   156  			rowEvent: &dmlsink.RowChangeCallbackableEvent{
   157  				Event:     row,
   158  				Callback:  func() {},
   159  				SinkState: &tableStatus,
   160  			},
   161  		}
   162  	}
   163  
   164  	// Test batching returns when the events count is equal to the batch size.
   165  	batch := make([]mqEvent, 512)
   166  	endIndex, err := worker.batch(ctx, batch, time.Minute)
   167  	require.NoError(t, err)
   168  	require.Equal(t, 512, endIndex)
   169  }
   170  
   171  func TestBatchEncode_Group(t *testing.T) {
   172  	t.Parallel()
   173  
   174  	key1 := model.TopicPartitionKey{
   175  		Topic:     "test",
   176  		Partition: 1,
   177  	}
   178  	key2 := model.TopicPartitionKey{
   179  		Topic:     "test",
   180  		Partition: 2,
   181  	}
   182  	key3 := model.TopicPartitionKey{
   183  		Topic:     "test1",
   184  		Partition: 2,
   185  	}
   186  	ctx, cancel := context.WithCancel(context.Background())
   187  	defer cancel()
   188  	worker, _ := newBatchEncodeWorker(ctx, t)
   189  	defer worker.close()
   190  
   191  	tableStatus := state.TableSinkSinking
   192  
   193  	tableInfo1 := model.BuildTableInfo("a", "b", []*model.Column{{Name: "col1", Type: 1}}, nil)
   194  	tableInfo2 := model.BuildTableInfo("aa", "bb", []*model.Column{{Name: "col1", Type: 1}}, nil)
   195  	tableInfo3 := model.BuildTableInfo("aaa", "bbb", []*model.Column{{Name: "col1", Type: 1}}, nil)
   196  	events := []mqEvent{
   197  		{
   198  			rowEvent: &dmlsink.RowChangeCallbackableEvent{
   199  				Event: &model.RowChangedEvent{
   200  					CommitTs:  1,
   201  					TableInfo: tableInfo1,
   202  					Columns:   model.Columns2ColumnDatas([]*model.Column{{Name: "col1", Value: "aa"}}, tableInfo1),
   203  				},
   204  				Callback:  func() {},
   205  				SinkState: &tableStatus,
   206  			},
   207  			key: key1,
   208  		},
   209  		{
   210  			rowEvent: &dmlsink.RowChangeCallbackableEvent{
   211  				Event: &model.RowChangedEvent{
   212  					CommitTs:  2,
   213  					TableInfo: tableInfo1,
   214  					Columns:   model.Columns2ColumnDatas([]*model.Column{{Name: "col1", Value: "bb"}}, tableInfo1),
   215  				},
   216  				Callback:  func() {},
   217  				SinkState: &tableStatus,
   218  			},
   219  			key: key1,
   220  		},
   221  		{
   222  			rowEvent: &dmlsink.RowChangeCallbackableEvent{
   223  				Event: &model.RowChangedEvent{
   224  					CommitTs:  3,
   225  					TableInfo: tableInfo1,
   226  					Columns:   model.Columns2ColumnDatas([]*model.Column{{Name: "col1", Value: "cc"}}, tableInfo1),
   227  				},
   228  				Callback:  func() {},
   229  				SinkState: &tableStatus,
   230  			},
   231  			key: key1,
   232  		},
   233  		{
   234  			rowEvent: &dmlsink.RowChangeCallbackableEvent{
   235  				Event: &model.RowChangedEvent{
   236  					CommitTs:  2,
   237  					TableInfo: tableInfo2,
   238  					Columns:   model.Columns2ColumnDatas([]*model.Column{{Name: "col1", Value: "bb"}}, tableInfo2),
   239  				},
   240  				Callback:  func() {},
   241  				SinkState: &tableStatus,
   242  			},
   243  			key: key2,
   244  		},
   245  		{
   246  			rowEvent: &dmlsink.RowChangeCallbackableEvent{
   247  				Event: &model.RowChangedEvent{
   248  					CommitTs:  2,
   249  					TableInfo: tableInfo3,
   250  					Columns:   model.Columns2ColumnDatas([]*model.Column{{Name: "col1", Value: "bb"}}, tableInfo3),
   251  				},
   252  				Callback:  func() {},
   253  				SinkState: &tableStatus,
   254  			},
   255  			key: key3,
   256  		},
   257  	}
   258  
   259  	partitionedRows := worker.group(events)
   260  	require.Len(t, partitionedRows, 3)
   261  	require.Len(t, partitionedRows[key1], 3)
   262  	// We must ensure that the sequence is not broken.
   263  	require.LessOrEqual(
   264  		t,
   265  		partitionedRows[key1][0].Event.GetCommitTs(), partitionedRows[key1][1].Event.GetCommitTs(),
   266  		partitionedRows[key1][2].Event.GetCommitTs(),
   267  	)
   268  	require.Len(t, partitionedRows[key2], 1)
   269  	require.Len(t, partitionedRows[key3], 1)
   270  }
   271  
   272  func TestBatchEncode_GroupWhenTableStopping(t *testing.T) {
   273  	helper := entry.NewSchemaTestHelper(t)
   274  	defer helper.Close()
   275  
   276  	sql := `create table test.t(a varchar(255) primary key)`
   277  	job := helper.DDL2Job(sql)
   278  	tableInfo := model.WrapTableInfo(0, "test", 1, job.BinlogInfo.TableInfo)
   279  
   280  	key1 := model.TopicPartitionKey{
   281  		Topic:     "test",
   282  		Partition: 1,
   283  	}
   284  	key2 := model.TopicPartitionKey{
   285  		Topic:     "test",
   286  		Partition: 2,
   287  	}
   288  	ctx, cancel := context.WithCancel(context.Background())
   289  	defer cancel()
   290  	worker, _ := newBatchEncodeWorker(ctx, t)
   291  	defer worker.close()
   292  	replicatingStatus := state.TableSinkSinking
   293  	stoppedStatus := state.TableSinkStopping
   294  	tableInfo2 := model.BuildTableInfo("a", "b", []*model.Column{{Name: "col1", Type: 1}}, nil)
   295  	events := []mqEvent{
   296  		{
   297  			rowEvent: &dmlsink.RowChangeCallbackableEvent{
   298  				Event: &model.RowChangedEvent{
   299  					CommitTs:  1,
   300  					TableInfo: tableInfo,
   301  					Columns:   model.Columns2ColumnDatas([]*model.Column{{Name: "a", Value: "aa"}}, tableInfo),
   302  				},
   303  				Callback:  func() {},
   304  				SinkState: &replicatingStatus,
   305  			},
   306  			key: key1,
   307  		},
   308  		{
   309  			rowEvent: &dmlsink.RowChangeCallbackableEvent{
   310  				Event: &model.RowChangedEvent{
   311  					CommitTs:  2,
   312  					TableInfo: tableInfo2,
   313  					Columns:   model.Columns2ColumnDatas([]*model.Column{{Name: "col1", Value: "bb"}}, tableInfo2),
   314  				},
   315  				Callback:  func() {},
   316  				SinkState: &replicatingStatus,
   317  			},
   318  			key: key1,
   319  		},
   320  		{
   321  			rowEvent: &dmlsink.RowChangeCallbackableEvent{
   322  				Event: &model.RowChangedEvent{
   323  					CommitTs:  3,
   324  					TableInfo: tableInfo2,
   325  					Columns:   model.Columns2ColumnDatas([]*model.Column{{Name: "col1", Value: "cc"}}, tableInfo2),
   326  				},
   327  				Callback:  func() {},
   328  				SinkState: &stoppedStatus,
   329  			},
   330  			key: key2,
   331  		},
   332  	}
   333  
   334  	partitionedRows := worker.group(events)
   335  	require.Len(t, partitionedRows, 1)
   336  	require.Len(t, partitionedRows[key1], 2)
   337  	// We must ensure that the sequence is not broken.
   338  	require.LessOrEqual(
   339  		t,
   340  		partitionedRows[key1][0].Event.GetCommitTs(),
   341  		partitionedRows[key1][1].Event.GetCommitTs(),
   342  	)
   343  }
   344  
   345  func TestBatchEncode_SendMessages(t *testing.T) {
   346  	key1 := model.TopicPartitionKey{
   347  		Topic:     "test",
   348  		Partition: 1,
   349  	}
   350  	key2 := model.TopicPartitionKey{
   351  		Topic:     "test",
   352  		Partition: 2,
   353  	}
   354  	key3 := model.TopicPartitionKey{
   355  		Topic:     "test1",
   356  		Partition: 2,
   357  	}
   358  
   359  	tableStatus := state.TableSinkSinking
   360  	ctx, cancel := context.WithCancel(context.Background())
   361  	defer cancel()
   362  	worker, p := newBatchEncodeWorker(ctx, t)
   363  	defer worker.close()
   364  
   365  	helper := entry.NewSchemaTestHelper(t)
   366  	defer helper.Close()
   367  
   368  	sql := `create table test.t(a varchar(255) primary key)`
   369  	job := helper.DDL2Job(sql)
   370  	tableInfo := model.WrapTableInfo(0, "test", 1, job.BinlogInfo.TableInfo)
   371  
   372  	events := []mqEvent{
   373  		{
   374  			rowEvent: &dmlsink.RowChangeCallbackableEvent{
   375  				Event: &model.RowChangedEvent{
   376  					CommitTs:  1,
   377  					TableInfo: tableInfo,
   378  					Columns:   model.Columns2ColumnDatas([]*model.Column{{Name: "a", Value: "aa"}}, tableInfo),
   379  				},
   380  				Callback:  func() {},
   381  				SinkState: &tableStatus,
   382  			},
   383  			key: key1,
   384  		},
   385  		{
   386  			rowEvent: &dmlsink.RowChangeCallbackableEvent{
   387  				Event: &model.RowChangedEvent{
   388  					CommitTs:  2,
   389  					TableInfo: tableInfo,
   390  					Columns:   model.Columns2ColumnDatas([]*model.Column{{Name: "a", Value: "bb"}}, tableInfo),
   391  				},
   392  				Callback:  func() {},
   393  				SinkState: &tableStatus,
   394  			},
   395  			key: key1,
   396  		},
   397  		{
   398  			rowEvent: &dmlsink.RowChangeCallbackableEvent{
   399  				Event: &model.RowChangedEvent{
   400  					CommitTs:  3,
   401  					TableInfo: tableInfo,
   402  					Columns:   model.Columns2ColumnDatas([]*model.Column{{Name: "a", Value: "cc"}}, tableInfo),
   403  				},
   404  				Callback:  func() {},
   405  				SinkState: &tableStatus,
   406  			},
   407  			key: key1,
   408  		},
   409  		{
   410  			rowEvent: &dmlsink.RowChangeCallbackableEvent{
   411  				Event: &model.RowChangedEvent{
   412  					CommitTs:  2,
   413  					TableInfo: tableInfo,
   414  					Columns:   model.Columns2ColumnDatas([]*model.Column{{Name: "a", Value: "bb"}}, tableInfo),
   415  				},
   416  				Callback:  func() {},
   417  				SinkState: &tableStatus,
   418  			},
   419  			key: key2,
   420  		},
   421  		{
   422  			rowEvent: &dmlsink.RowChangeCallbackableEvent{
   423  				Event: &model.RowChangedEvent{
   424  					CommitTs:  2,
   425  					TableInfo: tableInfo,
   426  					Columns:   model.Columns2ColumnDatas([]*model.Column{{Name: "a", Value: "bb"}}, tableInfo),
   427  				},
   428  				Callback:  func() {},
   429  				SinkState: &tableStatus,
   430  			},
   431  			key: key3,
   432  		},
   433  		{
   434  			rowEvent: &dmlsink.RowChangeCallbackableEvent{
   435  				Event: &model.RowChangedEvent{
   436  					CommitTs:  3,
   437  					TableInfo: tableInfo,
   438  					Columns:   model.Columns2ColumnDatas([]*model.Column{{Name: "a", Value: "bb"}}, tableInfo),
   439  				},
   440  				Callback:  func() {},
   441  				SinkState: &tableStatus,
   442  			},
   443  			key: key3,
   444  		},
   445  	}
   446  
   447  	var wg sync.WaitGroup
   448  	wg.Add(1)
   449  	go func() {
   450  		defer wg.Done()
   451  		_ = worker.run(ctx)
   452  	}()
   453  
   454  	for _, event := range events {
   455  		worker.msgChan.In() <- event
   456  	}
   457  
   458  	mp := p.(*dmlproducer.MockDMLProducer)
   459  	require.Eventually(t, func() bool {
   460  		return len(mp.GetAllEvents()) == len(events)
   461  	}, 3*time.Second, 100*time.Millisecond)
   462  	require.Eventually(t, func() bool {
   463  		return len(mp.GetEvents(key1.Topic, key1.Partition)) == 3
   464  	}, 3*time.Second, 100*time.Millisecond)
   465  	require.Eventually(t, func() bool {
   466  		return len(mp.GetEvents(key2.Topic, key2.Partition)) == 1
   467  	}, 3*time.Second, 100*time.Millisecond)
   468  	require.Eventually(t, func() bool {
   469  		return len(mp.GetEvents(key3.Topic, key3.Partition)) == 2
   470  	}, 3*time.Second, 100*time.Millisecond)
   471  
   472  	cancel()
   473  	wg.Wait()
   474  }
   475  
   476  func TestBatchEncodeWorker_Abort(t *testing.T) {
   477  	t.Parallel()
   478  
   479  	ctx, cancel := context.WithCancel(context.Background())
   480  	worker, _ := newBatchEncodeWorker(ctx, t)
   481  	defer worker.close()
   482  
   483  	var wg sync.WaitGroup
   484  	wg.Add(1)
   485  	go func() {
   486  		defer wg.Done()
   487  		err := worker.run(ctx)
   488  		require.Error(t, context.Canceled, err)
   489  	}()
   490  
   491  	cancel()
   492  	wg.Wait()
   493  }
   494  
   495  func TestNonBatchEncode_SendMessagesWhenTableStopping(t *testing.T) {
   496  	key1 := model.TopicPartitionKey{
   497  		Topic:     "test",
   498  		Partition: 1,
   499  	}
   500  	key2 := model.TopicPartitionKey{
   501  		Topic:     "test",
   502  		Partition: 2,
   503  	}
   504  	ctx, cancel := context.WithCancel(context.Background())
   505  	defer cancel()
   506  	worker, p := newNonBatchEncodeWorker(ctx, t)
   507  	defer worker.close()
   508  	replicatingStatus := state.TableSinkSinking
   509  	stoppedStatus := state.TableSinkStopping
   510  
   511  	helper := entry.NewSchemaTestHelper(t)
   512  	defer helper.Close()
   513  
   514  	sql := `create table test.t(a varchar(255) primary key)`
   515  	job := helper.DDL2Job(sql)
   516  	tableInfo := model.WrapTableInfo(0, "test", 1, job.BinlogInfo.TableInfo)
   517  
   518  	events := []mqEvent{
   519  		{
   520  			rowEvent: &dmlsink.RowChangeCallbackableEvent{
   521  				Event: &model.RowChangedEvent{
   522  					CommitTs:  1,
   523  					TableInfo: tableInfo,
   524  					Columns:   model.Columns2ColumnDatas([]*model.Column{{Name: "a", Type: mysql.TypeVarchar, Value: "aa"}}, tableInfo),
   525  				},
   526  				Callback:  func() {},
   527  				SinkState: &replicatingStatus,
   528  			},
   529  			key: key1,
   530  		},
   531  		{
   532  			rowEvent: &dmlsink.RowChangeCallbackableEvent{
   533  				Event: &model.RowChangedEvent{
   534  					CommitTs:  2,
   535  					TableInfo: tableInfo,
   536  					Columns:   model.Columns2ColumnDatas([]*model.Column{{Name: "a", Type: mysql.TypeVarchar, Value: "bb"}}, tableInfo),
   537  				},
   538  				Callback:  func() {},
   539  				SinkState: &replicatingStatus,
   540  			},
   541  			key: key1,
   542  		},
   543  		{
   544  			rowEvent: &dmlsink.RowChangeCallbackableEvent{
   545  				Event: &model.RowChangedEvent{
   546  					CommitTs:  3,
   547  					TableInfo: tableInfo,
   548  					Columns:   model.Columns2ColumnDatas([]*model.Column{{Name: "a", Type: mysql.TypeVarchar, Value: "cc"}}, tableInfo),
   549  				},
   550  				Callback:  func() {},
   551  				SinkState: &stoppedStatus,
   552  			},
   553  			key: key2,
   554  		},
   555  	}
   556  	for _, e := range events {
   557  		worker.msgChan.In() <- e
   558  	}
   559  
   560  	var wg sync.WaitGroup
   561  	wg.Add(1)
   562  	go func() {
   563  		defer wg.Done()
   564  		_ = worker.run(ctx)
   565  	}()
   566  	mp := p.(*dmlproducer.MockDMLProducer)
   567  	require.Eventually(t, func() bool {
   568  		return len(mp.GetAllEvents()) == 2
   569  	}, 3*time.Second, 100*time.Millisecond)
   570  	cancel()
   571  	wg.Wait()
   572  }
   573  
   574  func TestNonBatchEncodeWorker_Abort(t *testing.T) {
   575  	ctx, cancel := context.WithCancel(context.Background())
   576  	worker, _ := newBatchEncodeWorker(ctx, t)
   577  	defer worker.close()
   578  
   579  	var wg sync.WaitGroup
   580  	wg.Add(1)
   581  	go func() {
   582  		defer wg.Done()
   583  		err := worker.run(ctx)
   584  		require.Error(t, context.Canceled, err)
   585  	}()
   586  
   587  	cancel()
   588  	wg.Wait()
   589  }